From dd45690d47ad4d2685eec98859f0c056f057dff6 Mon Sep 17 00:00:00 2001 From: Benjamin Schaaf Date: Thu, 26 Nov 2020 00:34:57 +1100 Subject: [PATCH] First pass at integrating pipeline with application --- camera_config.c | 266 +++++ camera_config.h | 49 + config/pine64,pinephone-1.2.ini | 26 +- io_pipeline.c | 584 +++++++++ io_pipeline.h | 26 + main.c | 1991 +++++++++---------------------- main.h | 27 + meson.build | 10 +- process_pipeline.c | 467 ++++++++ process_pipeline.h | 30 + tools/test_camera.c | 2 +- 11 files changed, 2035 insertions(+), 1443 deletions(-) create mode 100644 camera_config.c create mode 100644 camera_config.h create mode 100644 io_pipeline.c create mode 100644 io_pipeline.h create mode 100644 main.h create mode 100644 process_pipeline.c create mode 100644 process_pipeline.h diff --git a/camera_config.c b/camera_config.c new file mode 100644 index 0000000..2a8284d --- /dev/null +++ b/camera_config.c @@ -0,0 +1,266 @@ +#include "camera_config.h" + +#include "ini.h" +#include "config.h" +#include +#include +#include +#include +#include +#include +#include + +static struct mp_camera_config cameras[MP_MAX_CAMERAS]; +static size_t num_cameras = 0; + +static char *exif_make; +static char *exif_model; + +static bool +find_config(char *conffile) +{ + char buf[512]; + char *xdg_config_home; + wordexp_t exp_result; + FILE *fp; + + // Resolve XDG stuff + if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) { + xdg_config_home = "~/.config"; + } + wordexp(xdg_config_home, &exp_result, 0); + xdg_config_home = strdup(exp_result.we_wordv[0]); + wordfree(&exp_result); + + if (access("/proc/device-tree/compatible", F_OK) != -1) { + // Reads to compatible string of the current device tree, looks like: + // pine64,pinephone-1.2\0allwinner,sun50i-a64\0 + fp = fopen("/proc/device-tree/compatible", "r"); + fgets(buf, 512, fp); + fclose(fp); + + // Check config/%dt.ini in the current working directory + sprintf(conffile, "config/%s.ini", buf); + if(access(conffile, F_OK) != -1) { + printf("Found config file at %s\n", conffile); + return true; + } + + // Check for a config file in XDG_CONFIG_HOME + sprintf(conffile, "%s/megapixels/config/%s.ini", xdg_config_home, buf); + if(access(conffile, F_OK) != -1) { + printf("Found config file at %s\n", conffile); + return true; + } + + // Check user overridden /etc/megapixels/config/$dt.ini + sprintf(conffile, "%s/megapixels/config/%s.ini", SYSCONFDIR, buf); + if(access(conffile, F_OK) != -1) { + printf("Found config file at %s\n", conffile); + return true; + } + // Check packaged /usr/share/megapixels/config/$dt.ini + sprintf(conffile, "%s/megapixels/config/%s.ini", DATADIR, buf); + if(access(conffile, F_OK) != -1) { + printf("Found config file at %s\n", conffile); + return true; + } + printf("%s not found\n", conffile); + } else { + printf("Could not read device name from device tree\n"); + } + + // If all else fails, fall back to /etc/megapixels.ini + sprintf(conffile, "/etc/megapixels.ini"); + if (access(conffile, F_OK) != -1) { + printf("Found config file at %s\n", conffile); + return true; + } + + return false; +} + +static int +strtoint(const char *nptr, char **endptr, int base) +{ + long x = strtol(nptr, endptr, base); + assert(x <= INT_MAX); + return (int) x; +} + +static bool +config_handle_camera_mode(const char *prefix, MPCameraMode * mode, const char *name, const char *value) +{ + int prefix_length = strlen(prefix); + if (strncmp(prefix, name, prefix_length) != 0) + return false; + + name += prefix_length; + + if (strcmp(name, "width") == 0) { + mode->width = strtoint(value, NULL, 10); + } else if (strcmp(name, "height") == 0) { + mode->height = strtoint(value, NULL, 10); + } else if (strcmp(name, "rate") == 0) { + mode->frame_interval.numerator = 1; + mode->frame_interval.denominator = strtoint(value, NULL, 10); + } else if (strcmp(name, "fmt") == 0) { + mode->pixel_format = mp_pixel_format_from_str(value); + if (mode->pixel_format == MP_PIXEL_FMT_UNSUPPORTED) { + g_printerr("Unsupported pixelformat %s\n", value); + exit(1); + } + } else { + return false; + } + return true; +} + +static int +config_ini_handler(void *user, const char *section, const char *name, + const char *value) +{ + if (strcmp(section, "device") == 0) { + if (strcmp(name, "make") == 0) { + exif_make = strdup(value); + } else if (strcmp(name, "model") == 0) { + exif_model = strdup(value); + } else { + g_printerr("Unknown key '%s' in [device]\n", name); + exit(1); + } + } else { + if (num_cameras == MP_MAX_CAMERAS) { + g_printerr("More cameras defined than NUM_CAMERAS\n"); + exit(1); + } + + size_t index = 0; + for (; index < num_cameras; ++index) { + if (strcmp(cameras[index].cfg_name, section) == 0) { + break; + } + } + + if (index == num_cameras) { + printf("Adding camera %s from config\n", section); + ++num_cameras; + + cameras[index].index = index; + strcpy(cameras[index].cfg_name, section); + } + + struct mp_camera_config *cc = &cameras[index]; + + if (config_handle_camera_mode("capture-", &cc->capture_mode, name, value)) { + } else if (config_handle_camera_mode("preview-", &cc->preview_mode, name, value)) { + } else if (strcmp(name, "rotate") == 0) { + cc->rotate = strtoint(value, NULL, 10); + } else if (strcmp(name, "mirrored") == 0) { + cc->mirrored = strcmp(value, "true") == 0; + } else if (strcmp(name, "driver") == 0) { + strcpy(cc->dev_name, value); + } else if (strcmp(name, "media-driver") == 0) { + strcpy(cc->media_dev_name, value); + } else if (strcmp(name, "media-links") == 0) { + char **linkdefs = g_strsplit(value, ",", 0); + + for (int i = 0; i < MP_MAX_LINKS && linkdefs[i] != NULL; ++i) { + char **linkdef = g_strsplit(linkdefs[i], "->", 2); + char **porta = g_strsplit(linkdef[0], ":", 2); + char **portb = g_strsplit(linkdef[1], ":", 2); + + strcpy(cc->media_links[i].source_name, porta[0]); + strcpy(cc->media_links[i].target_name, portb[0]); + cc->media_links[i].source_port = strtoint(porta[1], NULL, 10); + cc->media_links[i].target_port = strtoint(portb[1], NULL, 10); + + g_strfreev(portb); + g_strfreev(porta); + g_strfreev(linkdef); + ++cc->num_media_links; + } + g_strfreev(linkdefs); + } else if (strcmp(name, "colormatrix") == 0) { + sscanf(value, "%f,%f,%f,%f,%f,%f,%f,%f,%f", + cc->colormatrix+0, + cc->colormatrix+1, + cc->colormatrix+2, + cc->colormatrix+3, + cc->colormatrix+4, + cc->colormatrix+5, + cc->colormatrix+6, + cc->colormatrix+7, + cc->colormatrix+8 + ); + } else if (strcmp(name, "forwardmatrix") == 0) { + sscanf(value, "%f,%f,%f,%f,%f,%f,%f,%f,%f", + cc->forwardmatrix+0, + cc->forwardmatrix+1, + cc->forwardmatrix+2, + cc->forwardmatrix+3, + cc->forwardmatrix+4, + cc->forwardmatrix+5, + cc->forwardmatrix+6, + cc->forwardmatrix+7, + cc->forwardmatrix+8 + ); + } else if (strcmp(name, "whitelevel") == 0) { + cc->whitelevel = strtoint(value, NULL, 10); + } else if (strcmp(name, "blacklevel") == 0) { + cc->blacklevel = strtoint(value, NULL, 10); + } else if (strcmp(name, "focallength") == 0) { + cc->focallength = strtof(value, NULL); + } else if (strcmp(name, "cropfactor") == 0) { + cc->cropfactor = strtof(value, NULL); + } else if (strcmp(name, "fnumber") == 0) { + cc->fnumber = strtod(value, NULL); + } else if (strcmp(name, "iso-min") == 0) { + cc->iso_min = strtod(value, NULL); + } else if (strcmp(name, "iso-max") == 0) { + cc->iso_max = strtod(value, NULL); + } else { + g_printerr("Unknown key '%s' in [%s]\n", name, section); + exit(1); + } + } + return 1; +} + +bool mp_load_config() { + char file[512]; + if (!find_config(file)) { + g_printerr("Could not find any config file\n"); + return false; + } + + int result = ini_parse(file, config_ini_handler, NULL); + if (result == -1) { + g_printerr("Config file not found\n"); + } else if (result == -2) { + g_printerr("Could not allocate memory to parse config file\n"); + } else if (result != 0) { + g_printerr("Could not parse config file\n"); + } else { + return true; + } + return false; +} + +const char * mp_get_device_make() +{ + return exif_make; +} + +const char * mp_get_device_model() +{ + return exif_model; +} + +const struct mp_camera_config * mp_get_camera_config(size_t index) +{ + if (index >= num_cameras) + return NULL; + + return &cameras[index]; +} diff --git a/camera_config.h b/camera_config.h new file mode 100644 index 0000000..b2bb070 --- /dev/null +++ b/camera_config.h @@ -0,0 +1,49 @@ +#pragma once + +#include "camera.h" + +#include +#include + +#define MP_MAX_CAMERAS 5 +#define MP_MAX_LINKS 10 + +struct mp_media_link_config { + char source_name[100]; + char target_name[100]; + int source_port; + int target_port; +}; + +struct mp_camera_config { + size_t index; + + char cfg_name[100]; + char dev_name[260]; + char media_dev_name[260]; + + MPCameraMode capture_mode; + MPCameraMode preview_mode; + int rotate; + bool mirrored; + + struct mp_media_link_config media_links[MP_MAX_LINKS]; + int num_media_links; + + float colormatrix[9]; + float forwardmatrix[9]; + int blacklevel; + int whitelevel; + + float focallength; + float cropfactor; + double fnumber; + int iso_min; + int iso_max; +}; + +bool mp_load_config(); + +const char *mp_get_device_make(); +const char *mp_get_device_model(); +const struct mp_camera_config * mp_get_camera_config(size_t index); diff --git a/config/pine64,pinephone-1.2.ini b/config/pine64,pinephone-1.2.ini index b93df9b..841bd24 100644 --- a/config/pine64,pinephone-1.2.ini +++ b/config/pine64,pinephone-1.2.ini @@ -5,11 +5,16 @@ model=PinePhone [rear] driver=ov5640 media-driver=sun6i-csi -width=2592 -height=1944 -rate=15 -fmt=BGGR8 +capture-width=2592 +capture-height=1944 +capture-rate=15 +capture-fmt=BGGR8 +preview-width=1280 +preview-height=720 +preview-rate=30 +preview-fmt=BGGR8 rotate=270 +mirrored=false colormatrix=1.384,-0.3203,-0.0124,-0.2728,1.049,0.1556,-0.0506,0.2577,0.8050 forwardmatrix=0.7331,0.1294,0.1018,0.3039,0.6698,0.0263,0.0002,0.0556,0.7693 blacklevel=3 @@ -23,11 +28,16 @@ iso-max=64000 [front] driver=gc2145 media-driver=sun6i-csi -width=1280 -height=960 -rate=30 -fmt=BGGR8 +capture-width=1280 +capture-height=960 +capture-rate=30 +capture-fmt=BGGR8 +preview-width=1280 +preview-height=960 +preview-rate=30 +preview-fmt=BGGR8 rotate=90 +mirrored=true focallength=2.6 cropfactor=12.7 fnumber=2.8 diff --git a/io_pipeline.c b/io_pipeline.c new file mode 100644 index 0000000..138e894 --- /dev/null +++ b/io_pipeline.c @@ -0,0 +1,584 @@ +#include "io_pipeline.h" + +#include "device.h" +#include "camera.h" +#include "pipeline.h" +#include "process_pipeline.h" +#include +#include +#include +#include +#include +#include +#include + +struct media_link_info { + unsigned int source_entity_id; + unsigned int target_entity_id; + char source_fname[260]; + char target_fname[260]; +}; + +struct camera_info { + size_t device_index; + + unsigned int pad_id; + + char dev_fname[260]; + int fd; + + MPCamera *camera; + + int gain_ctrl; + int gain_max; + + bool has_auto_focus_continuous; + bool has_auto_focus_start; + + + // unsigned int entity_id; + // enum v4l2_buf_type type; + + // char media_dev_fname[260]; + // char video_dev_fname[260]; + // int media_fd; + + + // struct mp_media_link media_links[MP_MAX_LINKS]; + // int num_media_links; + + // int gain_ctrl; +}; + +struct device_info { + const char *media_dev_name; // owned by camera config + + MPDevice *device; + + unsigned int interface_pad_id; + + int video_fd; +}; + +static struct camera_info cameras[MP_MAX_CAMERAS]; + +static struct device_info devices[MP_MAX_CAMERAS]; +static size_t num_devices = 0; + +static const struct mp_camera_config *camera = NULL; +static MPCameraMode mode; + +static bool just_switched_mode = false; +static int blank_frame_count = 0; + +static int burst_length; +static int captures_remaining = 0; + +static int preview_width; +static int preview_height; + +struct control_state { + bool gain_is_manual; + int gain; + + bool exposure_is_manual; + int exposure; +}; + +static struct control_state desired_controls = {}; +static struct control_state current_controls = {}; + +static bool want_focus = false; + +static MPPipeline *pipeline; +static GSource *capture_source; + +static int +xioctl(int fd, int request, void *arg) +{ + int r; + do { + r = ioctl(fd, request, arg); + } while (r == -1 && errno == EINTR); + return r; +} + +static int +v4l2_ctrl_set(int fd, uint32_t id, int val) +{ + struct v4l2_control ctrl = {0}; + ctrl.id = id; + ctrl.value = val; + + if (xioctl(fd, VIDIOC_S_CTRL, &ctrl) == -1) { + g_printerr("Failed to set control %d to %d\n", id, val); + return -1; + } + return 0; +} + +static int +v4l2_ctrl_get(int fd, uint32_t id) +{ + struct v4l2_control ctrl = {0}; + ctrl.id = id; + + if (xioctl(fd, VIDIOC_G_CTRL, &ctrl) == -1) { + g_printerr("Failed to get control %d\n", id); + return -1; + } + return ctrl.value; +} + +static int +v4l2_ctrl_get_max(int fd, uint32_t id) +{ + struct v4l2_queryctrl queryctrl; + int ret; + + memset(&queryctrl, 0, sizeof(queryctrl)); + + queryctrl.id = id; + ret = xioctl(fd, VIDIOC_QUERYCTRL, &queryctrl); + if (ret) + return 0; + + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) { + return 0; + } + + return queryctrl.maximum; +} + +static int +v4l2_has_control(int fd, int control_id) +{ + struct v4l2_queryctrl queryctrl; + int ret; + + memset(&queryctrl, 0, sizeof(queryctrl)); + + queryctrl.id = control_id; + ret = xioctl(fd, VIDIOC_QUERYCTRL, &queryctrl); + if (ret) + return 0; + + if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) { + return 0; + } + + return 1; +} + +static void setup_camera(MPDeviceList **device_list, const struct mp_camera_config *config) +{ + // Find device info + size_t device_index = 0; + for (; device_index < num_devices; ++device_index) { + if (strcmp(config->media_dev_name, devices[device_index].media_dev_name) == 0) { + break; + } + } + + if (device_index == num_devices) + { + device_index = num_devices; + + // Initialize new device + struct device_info *info = &devices[device_index]; + info->media_dev_name = config->media_dev_name; + info->device = mp_device_list_find_remove(device_list, info->media_dev_name); + if (!info->device) { + g_printerr("Could not find /dev/media* node matching '%s'\n", info->media_dev_name); + exit(EXIT_FAILURE); + } + + const struct media_v2_entity *entity = mp_device_find_entity(info->device, info->media_dev_name); + if (!entity) { + g_printerr("Count not find device video entity\n"); + exit(EXIT_FAILURE); + } + + const struct media_v2_pad *pad = mp_device_get_pad_from_entity(info->device, entity->id); + info->interface_pad_id = pad->id; + + const struct media_v2_interface *interface = mp_device_find_entity_interface(info->device, entity->id); + char dev_name[260]; + if (!mp_find_device_path(interface->devnode, dev_name, 260)) { + g_printerr("Count not find video path\n"); + exit(EXIT_FAILURE); + } + + info->video_fd = open(dev_name, O_RDWR); + if (info->video_fd == -1) { + g_printerr("Could not open %s\n", dev_name); + exit(EXIT_FAILURE); + } + + ++num_devices; + } + + { + struct camera_info *info = &cameras[config->index]; + struct device_info *dev_info = &devices[device_index]; + + info->device_index = device_index; + + const struct media_v2_entity *entity = mp_device_find_entity(dev_info->device, config->dev_name); + if (!entity) { + g_printerr("Count not find camera entity matching '%s'\n", config->dev_name); + exit(EXIT_FAILURE); + } + + const struct media_v2_pad *pad = mp_device_get_pad_from_entity(dev_info->device, entity->id); + + info->pad_id = pad->id; + + // Make sure the camera starts out as disabled + mp_device_setup_link( + dev_info->device, + info->pad_id, + dev_info->interface_pad_id, + false); + + const struct media_v2_interface *interface = mp_device_find_entity_interface(dev_info->device, entity->id); + + if (!mp_find_device_path(interface->devnode, info->dev_fname, 260)) { + g_printerr("Count not find camera device path\n"); + exit(EXIT_FAILURE); + } + + info->fd = open(info->dev_fname, O_RDWR); + if (info->fd == -1) { + g_printerr("Could not open %s\n", info->dev_fname); + exit(EXIT_FAILURE); + } + + info->camera = mp_camera_new(dev_info->video_fd, info->fd); + + // Trigger continuous auto focus if the sensor supports it + if (v4l2_has_control(info->fd, V4L2_CID_FOCUS_AUTO)) { + info->has_auto_focus_continuous = true; + v4l2_ctrl_set(info->fd, V4L2_CID_FOCUS_AUTO, 1); + } + if (v4l2_has_control(info->fd, V4L2_CID_AUTO_FOCUS_START)) { + info->has_auto_focus_start = true; + } + + if (v4l2_has_control(info->fd, V4L2_CID_GAIN)) { + info->gain_ctrl = V4L2_CID_GAIN; + info->gain_max = v4l2_ctrl_get_max(info->fd, V4L2_CID_GAIN); + } + + if (v4l2_has_control(info->fd, V4L2_CID_ANALOGUE_GAIN)) { + info->gain_ctrl = V4L2_CID_ANALOGUE_GAIN; + info->gain_max = v4l2_ctrl_get_max(info->fd, V4L2_CID_ANALOGUE_GAIN); + } + } +} + +static void setup(MPPipeline *pipeline, const void *data) +{ + MPDeviceList *device_list = mp_device_list_new(); + + for (size_t i = 0; i < MP_MAX_CAMERAS; ++i) { + const struct mp_camera_config *config = mp_get_camera_config(i); + if (!config) { + break; + } + + setup_camera(&device_list, config); + } + + mp_device_list_free(device_list); +} + +void mp_io_pipeline_start() +{ + mp_process_pipeline_start(); + + pipeline = mp_pipeline_new(); + + mp_pipeline_invoke(pipeline, setup, NULL, 0); +} + +void mp_io_pipeline_stop() +{ + if (capture_source) { + g_source_destroy(capture_source); + } + + mp_pipeline_free(pipeline); + + mp_process_pipeline_stop(); +} + +static void +update_process_pipeline() +{ + struct camera_info *info = &cameras[camera->index]; + + // Grab the latest control values + if (!current_controls.gain_is_manual) { + current_controls.gain = v4l2_ctrl_get(info->fd, info->gain_ctrl); + } + if (!current_controls.exposure_is_manual) { + current_controls.exposure = v4l2_ctrl_get(info->fd, V4L2_CID_EXPOSURE); + } + + struct mp_process_pipeline_state pipeline_state = { + .camera = camera, + .mode = mode, + .burst_length = burst_length, + .preview_width = preview_width, + .preview_height = preview_height, + .gain_is_manual = current_controls.gain_is_manual, + .gain = current_controls.gain, + .gain_max = info->gain_max, + .exposure_is_manual = current_controls.exposure_is_manual, + .exposure = current_controls.exposure, + .has_auto_focus_continuous = info->has_auto_focus_continuous, + .has_auto_focus_start = info->has_auto_focus_start, + }; + mp_process_pipeline_update_state(&pipeline_state); +} + +static void +focus(MPPipeline *pipeline, const void *data) +{ + want_focus = true; +} + +void mp_io_pipeline_focus() +{ + mp_pipeline_invoke(pipeline, focus, NULL, 0); +} + +static void +capture(MPPipeline *pipeline, const void *data) +{ + struct camera_info *info = &cameras[camera->index]; + + captures_remaining = burst_length; + + // Disable the autogain/exposure while taking the burst + v4l2_ctrl_set(info->fd, V4L2_CID_AUTOGAIN, 0); + v4l2_ctrl_set(info->fd, V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL); + + // Change camera mode for capturing + mp_camera_stop_capture(info->camera); + + mode = camera->capture_mode; + mp_camera_set_mode(info->camera, &mode); + just_switched_mode = true; + + mp_camera_start_capture(info->camera); + + update_process_pipeline(); + + mp_process_pipeline_capture(); +} + +void mp_io_pipeline_capture() +{ + mp_pipeline_invoke(pipeline, capture, NULL, 0); +} + +static void +update_controls() +{ + // Don't update controls while capturing + if (captures_remaining > 0) { + return; + } + + struct camera_info *info = &cameras[camera->index]; + + if (want_focus) { + if (info->has_auto_focus_continuous) { + v4l2_ctrl_set(info->fd, V4L2_CID_FOCUS_AUTO, 1); + } else if (info->has_auto_focus_start) { + v4l2_ctrl_set(info->fd, V4L2_CID_AUTO_FOCUS_START, 1); + } + + want_focus = false; + } + + if (current_controls.gain_is_manual != desired_controls.gain_is_manual) { + v4l2_ctrl_set(info->fd, V4L2_CID_AUTOGAIN, desired_controls.gain_is_manual ? 0 : 1); + } + + if (!desired_controls.gain_is_manual && current_controls.gain != desired_controls.gain) { + v4l2_ctrl_set(info->fd, info->gain_ctrl, desired_controls.gain); + } + + if (current_controls.exposure_is_manual != desired_controls.exposure_is_manual) { + v4l2_ctrl_set( + info->fd, + V4L2_CID_EXPOSURE_AUTO, + desired_controls.exposure_is_manual ? V4L2_EXPOSURE_MANUAL : V4L2_EXPOSURE_AUTO); + } + + if (!desired_controls.exposure_is_manual && current_controls.exposure != desired_controls.exposure) { + printf("Setting exposure to %d\n", desired_controls.exposure); + v4l2_ctrl_set(info->fd, V4L2_CID_EXPOSURE, desired_controls.exposure); + } + + current_controls = desired_controls; +} + +static void +on_frame(MPImage image, void *data) +{ + // Only update controls right after a frame was captured + update_controls(); + + // When the mode is switched while capturing we get a couple blank frames, + // presumably from buffers made ready during the switch. Ignore these. + if (just_switched_mode) + { + if (blank_frame_count < 20) { + // Only check a 50x50 area + size_t test_size = MIN(50, image.width) * MIN(50, image.height); + + bool image_is_blank = true; + for (size_t i = 0; i < test_size; ++i) { + if (image.data[i] != 0) { + image_is_blank = false; + } + } + + if (image_is_blank) { + printf("Skipping blank image\n"); + ++blank_frame_count; + return; + } + } else { + printf("Blank image limit reached, resulting capture may be blank\n"); + } + + just_switched_mode = false; + blank_frame_count = 0; + } + + // Copy from the camera buffer + size_t size = mp_pixel_format_width_to_bytes(image.pixel_format, image.width) * image.height; + uint8_t *buffer = malloc(size); + memcpy(buffer, image.data, size); + + image.data = buffer; + + // Send the image off for processing + mp_process_pipeline_process_image(image); + + if (captures_remaining > 0) { + --captures_remaining; + + if (captures_remaining == 0) { + struct camera_info *info = &cameras[camera->index]; + + // Restore the auto exposure and gain if needed + if (!current_controls.exposure_is_manual) { + v4l2_ctrl_set(info->fd, V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_AUTO); + } + + if (!current_controls.gain_is_manual) { + v4l2_ctrl_set(info->fd, V4L2_CID_AUTOGAIN, 1); + } + + // Go back to preview mode + mp_camera_stop_capture(info->camera); + + mode = camera->preview_mode; + mp_camera_set_mode(info->camera, &mode); + just_switched_mode = true; + + mp_camera_start_capture(info->camera); + + update_process_pipeline(); + } + } +} + +static void +update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state) +{ + // Make sure the state isn't updated more than it needs to be by checking + // whether this state change actually changes anything. + bool has_changed = false; + + if (camera != state->camera) { + has_changed = true; + + if (camera) { + struct camera_info *info = &cameras[camera->index]; + struct device_info *dev_info = &devices[info->device_index]; + + mp_camera_stop_capture(info->camera); + mp_device_setup_link( + dev_info->device, + info->pad_id, + dev_info->interface_pad_id, + false); + } + + if (capture_source) { + g_source_destroy(capture_source); + capture_source = NULL; + } + + camera = state->camera; + + if (camera) { + struct camera_info *info = &cameras[camera->index]; + struct device_info *dev_info = &devices[info->device_index]; + + mp_device_setup_link( + dev_info->device, + info->pad_id, + dev_info->interface_pad_id, + true); + + mode = camera->preview_mode; + mp_camera_set_mode(info->camera, &mode); + + mp_camera_start_capture(info->camera); + capture_source = mp_pipeline_add_capture_source(pipeline, info->camera, on_frame, NULL); + + current_controls.gain_is_manual = v4l2_ctrl_get(info->fd, V4L2_CID_EXPOSURE_AUTO) == V4L2_EXPOSURE_MANUAL; + current_controls.gain = v4l2_ctrl_get(info->fd, info->gain_ctrl); + + current_controls.exposure_is_manual = v4l2_ctrl_get(info->fd, V4L2_CID_AUTOGAIN) == 0; + current_controls.exposure = v4l2_ctrl_get(info->fd, V4L2_CID_EXPOSURE); + } + } + + has_changed = has_changed + || burst_length != state->burst_length + || preview_width != state->preview_width + || preview_height != state->preview_height; + + burst_length = state->burst_length; + preview_width = state->preview_width; + preview_height = state->preview_height; + + if (camera) { + struct control_state previous_desired = desired_controls; + + desired_controls.gain_is_manual = state->gain_is_manual; + desired_controls.gain = state->gain; + desired_controls.exposure_is_manual = state->exposure_is_manual; + desired_controls.exposure = state->exposure; + + has_changed = has_changed || memcmp(&previous_desired, &desired_controls, sizeof(struct control_state)) != 0; + } + + assert(has_changed); + + update_process_pipeline(); +} + +void mp_io_pipeline_update_state(const struct mp_io_pipeline_state *state) +{ + mp_pipeline_invoke(pipeline, (MPPipelineCallback)update_state, state, sizeof(struct mp_io_pipeline_state)); +} diff --git a/io_pipeline.h b/io_pipeline.h new file mode 100644 index 0000000..9962174 --- /dev/null +++ b/io_pipeline.h @@ -0,0 +1,26 @@ +#pragma once + +#include "camera_config.h" + +struct mp_io_pipeline_state { + const struct mp_camera_config *camera; + + int burst_length; + + int preview_width; + int preview_height; + + bool gain_is_manual; + int gain; + + bool exposure_is_manual; + int exposure; +}; + +void mp_io_pipeline_start(); +void mp_io_pipeline_stop(); + +void mp_io_pipeline_focus(); +void mp_io_pipeline_capture(); + +void mp_io_pipeline_update_state(const struct mp_io_pipeline_state *state); diff --git a/main.c b/main.c index 6954c06..4edb0a1 100644 --- a/main.c +++ b/main.c @@ -1,3 +1,5 @@ +#include "main.h" + #include #include #include @@ -14,116 +16,41 @@ #include #include #include -#include #include -#include "config.h" -#include "ini.h" +#include "camera_config.h" #include "quickpreview.h" - -#define NUM_CAMERAS 5 -#define NUM_LINKS 10 +#include "io_pipeline.h" enum user_control { USER_CONTROL_ISO, USER_CONTROL_SHUTTER }; -#define TIFFTAG_FORWARDMATRIX1 50964 +static bool camera_is_initialized = false; +static const struct mp_camera_config *camera = NULL; +static MPCameraMode mode; -struct buffer { - void *start; - size_t length; -}; - -struct mp_media_link { - char source_name[100]; - char target_name[100]; - int source_port; - int target_port; - unsigned int source_entity_id; - unsigned int target_entity_id; - char source_fname[260]; - char target_fname[260]; - int valid; -}; - -struct camerainfo { - char cfg_name[100]; - int exists; - char dev_name[260]; - char dev_fname[260]; - unsigned int entity_id; - int width; - int height; - int rate; - int rotate; - int fmt; - int mbus; - enum v4l2_buf_type type; - int fd; - - char media_dev_name[260]; - char media_dev_fname[260]; - char video_dev_fname[260]; - int media_fd; - int video_fd; - unsigned int interface_entity_id; - - struct mp_media_link media_links[NUM_LINKS]; - - float colormatrix[9]; - float forwardmatrix[9]; - int blacklevel; - int whitelevel; - - float focallength; - float cropfactor; - double fnumber; - int iso_min; - int iso_max; - - int gain_ctrl; - int gain_max; - - int has_af_c; - int has_af_s; -}; - -struct camerainfo cameras[NUM_CAMERAS]; - -static float colormatrix_srgb[] = { - 3.2409, -1.5373, -0.4986, - -0.9692, 1.8759, 0.0415, - 0.0556, -0.2039, 1.0569 -}; - -struct buffer *buffers; -struct v4l2_plane buf_planes[1]; -static unsigned int n_buffers; - -struct camerainfo current; - -// General info -static char *exif_make; -static char *exif_model; - -// State -static int ready = 0; -static int capture = 0; -static int current_cid = -1; -static cairo_surface_t *surface = NULL; -static cairo_surface_t *status_surface = NULL; static int preview_width = -1; static int preview_height = -1; + +static bool gain_is_manual = false; +static int gain; +static int gain_max; + +static bool exposure_is_manual = false; +static int exposure; + +static bool has_auto_focus_continuous; +static bool has_auto_focus_start; + +static cairo_surface_t *surface = NULL; +static cairo_surface_t *status_surface = NULL; static char last_path[260] = ""; -static int auto_exposure = 1; -static int exposure = 1; -static int auto_gain = 1; -static int gain = 1; -static int burst_length = 10; -static char burst_dir[23]; -static char processing_script[512]; + +static int burst_length = 3; + static enum user_control current_control; + // Widgets GtkWidget *preview; GtkWidget *error_box; @@ -135,30 +62,6 @@ GtkWidget *control_name; GtkAdjustment *control_slider; GtkWidget *control_auto; -static int -xioctl(int fd, int request, void *arg) -{ - int r; - do { - r = ioctl(fd, request, arg); - } while (r == -1 && errno == EINTR); - return r; -} - -static void -errno_exit(const char *s) -{ - fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno)); - exit(EXIT_FAILURE); -} - -static void -show_error(const char *s) -{ - gtk_label_set_text(GTK_LABEL(error_message), s); - gtk_widget_show(error_box); -} - int remap(int value, int input_min, int input_max, int output_min, int output_max) { @@ -177,186 +80,214 @@ remap(int value, int input_min, int input_max, int output_min, int output_max) } static void -start_capturing(int fd) +update_io_pipeline() { - for (int i = 0; i < n_buffers; ++i) { - struct v4l2_buffer buf = { - .type = current.type, - .memory = V4L2_MEMORY_MMAP, - .index = i, - }; - - if(current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - buf.m.planes = buf_planes; - buf.length = 1; - } - - if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { - errno_exit("VIDIOC_QBUF"); - } - } - - if (xioctl(fd, VIDIOC_STREAMON, ¤t.type) == -1) { - errno_exit("VIDIOC_STREAMON"); - } - - ready = 1; -} - -static void -stop_capturing(int fd) -{ - int i; - ready = 0; - printf("Stopping capture\n"); - - if (xioctl(fd, VIDIOC_STREAMOFF, ¤t.type) == -1) { - errno_exit("VIDIOC_STREAMOFF"); - } - - for (i = 0; i < n_buffers; ++i) { - munmap(buffers[i].start, buffers[i].length); - } - -} - -static void -init_mmap(int fd) -{ - struct v4l2_requestbuffers req = { - .count = 4, - .type = current.type, - .memory = V4L2_MEMORY_MMAP, + struct mp_io_pipeline_state io_state = { + .camera = camera, + .burst_length = burst_length, + .preview_width = preview_width, + .preview_height = preview_height, + .gain_is_manual = gain_is_manual, + .gain = gain, + .exposure_is_manual = exposure_is_manual, + .exposure = exposure, }; - - if (xioctl(fd, VIDIOC_REQBUFS, &req) == -1) { - if (errno == EINVAL) { - fprintf(stderr, "%s does not support memory mapping", - current.dev_name); - exit(EXIT_FAILURE); - } else { - errno_exit("VIDIOC_REQBUFS"); - } - } - - if (req.count < 2) { - fprintf(stderr, "Insufficient buffer memory on %s\n", - current.dev_name); - exit(EXIT_FAILURE); - } - - buffers = calloc(req.count, sizeof(buffers[0])); - - if (!buffers) { - fprintf(stderr, "Out of memory\\n"); - exit(EXIT_FAILURE); - } - - for (n_buffers = 0; n_buffers < req.count; ++n_buffers) { - struct v4l2_buffer buf = { - .type = current.type, - .memory = V4L2_MEMORY_MMAP, - .index = n_buffers, - }; - - if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - buf.m.planes = buf_planes; - buf.length = 1; - } - - if (xioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) { - errno_exit("VIDIOC_QUERYBUF"); - } - - if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - buffers[n_buffers].length = buf.m.planes[0].length; - buffers[n_buffers].start = mmap(NULL /* start anywhere */, - buf.m.planes[0].length, - PROT_READ | PROT_WRITE /* required */, - MAP_SHARED /* recommended */, - fd, buf.m.planes[0].m.mem_offset); - } else { - buffers[n_buffers].length = buf.length; - buffers[n_buffers].start = mmap(NULL /* start anywhere */, - buf.length, - PROT_READ | PROT_WRITE /* required */, - MAP_SHARED /* recommended */, - fd, buf.m.offset); - } - - if (MAP_FAILED == buffers[n_buffers].start) { - errno_exit("mmap"); - } - } + mp_io_pipeline_update_state(&io_state); } -static int -v4l2_ctrl_set(int fd, uint32_t id, int val) +static bool +update_state(const struct mp_main_state *state) { - struct v4l2_control ctrl = {0}; - ctrl.id = id; - ctrl.value = val; - - if (xioctl(fd, VIDIOC_S_CTRL, &ctrl) == -1) { - g_printerr("Failed to set control %d to %d\n", id, val); - return -1; + if (!camera_is_initialized) { + camera_is_initialized = true; } - return 0; + + if (camera == state->camera) { + mode = state->mode; + + if (!gain_is_manual) { + gain = state->gain; + } + gain_max = state->gain_max; + + if (!exposure_is_manual) { + exposure = state->exposure; + } + + has_auto_focus_continuous = state->has_auto_focus_continuous; + has_auto_focus_start = state->has_auto_focus_start; + } + + return false; } -static int -v4l2_ctrl_get(int fd, uint32_t id) +void mp_main_update_state(const struct mp_main_state *state) { - struct v4l2_control ctrl = {0}; - ctrl.id = id; + struct mp_main_state *state_copy = malloc(sizeof(struct mp_main_state)); + *state_copy = *state; - if (xioctl(fd, VIDIOC_G_CTRL, &ctrl) == -1) { - g_printerr("Failed to get control %d\n", id); - return -1; - } - return ctrl.value; + g_main_context_invoke_full( + g_main_context_default(), + G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc)update_state, + state_copy, + free); } -static int -v4l2_ctrl_get_max(int fd, uint32_t id) +static bool +set_preview(cairo_surface_t *image) { - struct v4l2_queryctrl queryctrl; - int ret; - - memset(&queryctrl, 0, sizeof(queryctrl)); - - queryctrl.id = id; - ret = xioctl(fd, VIDIOC_QUERYCTRL, &queryctrl); - if (ret) - return 0; - - if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) { - return 0; + if (surface) { + cairo_surface_destroy(surface); } - - return queryctrl.maximum; + surface = image; + gtk_widget_queue_draw(preview); + return false; } -static int -v4l2_has_control(int fd, int control_id) +void mp_main_set_preview(cairo_surface_t *image) { - struct v4l2_queryctrl queryctrl; - int ret; - - memset(&queryctrl, 0, sizeof(queryctrl)); - - queryctrl.id = control_id; - ret = xioctl(fd, VIDIOC_QUERYCTRL, &queryctrl); - if (ret) - return 0; - - if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) { - return 0; - } - - return 1; + g_main_context_invoke_full( + g_main_context_default(), + G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc)set_preview, + image, + NULL); } +static bool +capture_completed(const char *fname) +{ + return false; +} + +void mp_main_capture_completed(const char *fname) +{ + gchar *name = g_strdup(fname); + + g_main_context_invoke_full( + g_main_context_default(), + G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc)capture_completed, + name, + g_free); +} + +// static void +// start_capturing(int fd) +// { +// for (int i = 0; i < n_buffers; ++i) { +// struct v4l2_buffer buf = { +// .type = current.type, +// .memory = V4L2_MEMORY_MMAP, +// .index = i, +// }; + +// if(current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { +// buf.m.planes = buf_planes; +// buf.length = 1; +// } + +// if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { +// errno_exit("VIDIOC_QBUF"); +// } +// } + +// if (xioctl(fd, VIDIOC_STREAMON, ¤t.type) == -1) { +// errno_exit("VIDIOC_STREAMON"); +// } + +// ready = 1; +// } + +// static void +// stop_capturing(int fd) +// { +// int i; +// ready = 0; +// printf("Stopping capture\n"); + +// if (xioctl(fd, VIDIOC_STREAMOFF, ¤t.type) == -1) { +// errno_exit("VIDIOC_STREAMOFF"); +// } + +// for (i = 0; i < n_buffers; ++i) { +// munmap(buffers[i].start, buffers[i].length); +// } + +// } + +// static void +// init_mmap(int fd) +// { +// struct v4l2_requestbuffers req = { +// .count = 4, +// .type = current.type, +// .memory = V4L2_MEMORY_MMAP, +// }; + +// if (xioctl(fd, VIDIOC_REQBUFS, &req) == -1) { +// if (errno == EINVAL) { +// fprintf(stderr, "%s does not support memory mapping", +// current.dev_name); +// exit(EXIT_FAILURE); +// } else { +// errno_exit("VIDIOC_REQBUFS"); +// } +// } + +// if (req.count < 2) { +// fprintf(stderr, "Insufficient buffer memory on %s\n", +// current.dev_name); +// exit(EXIT_FAILURE); +// } + +// buffers = calloc(req.count, sizeof(buffers[0])); + +// if (!buffers) { +// fprintf(stderr, "Out of memory\\n"); +// exit(EXIT_FAILURE); +// } + +// for (n_buffers = 0; n_buffers < req.count; ++n_buffers) { +// struct v4l2_buffer buf = { +// .type = current.type, +// .memory = V4L2_MEMORY_MMAP, +// .index = n_buffers, +// }; + +// if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { +// buf.m.planes = buf_planes; +// buf.length = 1; +// } + +// if (xioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) { +// errno_exit("VIDIOC_QUERYBUF"); +// } + +// if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { +// buffers[n_buffers].length = buf.m.planes[0].length; +// buffers[n_buffers].start = mmap(NULL /* start anywhere */, +// buf.m.planes[0].length, +// PROT_READ | PROT_WRITE /* required */, +// MAP_SHARED /* recommended */, +// fd, buf.m.planes[0].m.mem_offset); +// } else { +// buffers[n_buffers].length = buf.length; +// buffers[n_buffers].start = mmap(NULL /* start anywhere */, +// buf.length, +// PROT_READ | PROT_WRITE /* required */, +// MAP_SHARED /* recommended */, +// fd, buf.m.offset); +// } + +// if (MAP_FAILED == buffers[n_buffers].start) { +// errno_exit("mmap"); +// } +// } +// } + + static void draw_controls() { @@ -365,18 +296,18 @@ draw_controls() int temp; char shutterangle[6]; - if (auto_exposure) { - sprintf(shutterangle, "auto"); - } else { - temp = (int)((float)exposure / (float)current.height * 360); + if (exposure_is_manual) { + temp = (int)((float)exposure / (float)camera->capture_mode.height * 360); sprintf(shutterangle, "%d\u00b0", temp); + } else { + sprintf(shutterangle, "auto"); } - if (auto_gain) { - sprintf(iso, "auto"); - } else { - temp = remap(gain - 1, 0, current.gain_max, current.iso_min, current.iso_max); + if (gain_is_manual) { + temp = remap(gain - 1, 0, gain_max, camera->iso_min, camera->iso_max); sprintf(iso, "%d", temp); + } else { + sprintf(iso, "auto"); } if (status_surface) @@ -436,511 +367,254 @@ draw_controls() cairo_destroy(cr); + gtk_widget_queue_draw_area(preview, 0, 0, preview_width, 32); } -static void -init_sensor(char *fn, int width, int height, int mbus, int rate) -{ - int fd; - struct v4l2_subdev_frame_interval interval = {}; - struct v4l2_subdev_format fmt = {}; - fd = open(fn, O_RDWR); +// static void +// init_sensor(char *fn, int width, int height, int mbus, int rate) +// { +// int fd; +// struct v4l2_subdev_frame_interval interval = {}; +// struct v4l2_subdev_format fmt = {}; +// fd = open(fn, O_RDWR); - g_print("Setting sensor rate to %d\n", rate); - interval.pad = 0; - interval.interval.numerator = 1; - interval.interval.denominator = rate; +// g_print("Setting sensor rate to %d\n", rate); +// interval.pad = 0; +// interval.interval.numerator = 1; +// interval.interval.denominator = rate; - if (xioctl(fd, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &interval) == -1) { - errno_exit("VIDIOC_SUBDEV_S_FRAME_INTERVAL"); - } +// if (xioctl(fd, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &interval) == -1) { +// errno_exit("VIDIOC_SUBDEV_S_FRAME_INTERVAL"); +// } - if (interval.interval.numerator != 1 || interval.interval.denominator != rate) - g_printerr("Driver chose %d/%d instead\n", - interval.interval.numerator, interval.interval.denominator); +// if (interval.interval.numerator != 1 || interval.interval.denominator != rate) +// g_printerr("Driver chose %d/%d instead\n", +// interval.interval.numerator, interval.interval.denominator); - g_print("Setting sensor to %dx%d fmt %d\n", - width, height, mbus); - fmt.pad = 0; - fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; - fmt.format.code = mbus; - fmt.format.width = width; - fmt.format.height = height; - fmt.format.field = V4L2_FIELD_ANY; +// g_print("Setting sensor to %dx%d fmt %d\n", +// width, height, mbus); +// fmt.pad = 0; +// fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; +// fmt.format.code = mbus; +// fmt.format.width = width; +// fmt.format.height = height; +// fmt.format.field = V4L2_FIELD_ANY; - if (xioctl(fd, VIDIOC_SUBDEV_S_FMT, &fmt) == -1) { - errno_exit("VIDIOC_SUBDEV_S_FMT"); - } - if (fmt.format.width != width || fmt.format.height != height || fmt.format.code != mbus) - g_printerr("Driver chose %dx%d fmt %d instead\n", - fmt.format.width, fmt.format.height, - fmt.format.code); +// if (xioctl(fd, VIDIOC_SUBDEV_S_FMT, &fmt) == -1) { +// errno_exit("VIDIOC_SUBDEV_S_FMT"); +// } +// if (fmt.format.width != width || fmt.format.height != height || fmt.format.code != mbus) +// g_printerr("Driver chose %dx%d fmt %d instead\n", +// fmt.format.width, fmt.format.height, +// fmt.format.code); - // Trigger continuous auto focus if the sensor supports it - if (v4l2_has_control(fd, V4L2_CID_FOCUS_AUTO)) { - current.has_af_c = 1; - v4l2_ctrl_set(fd, V4L2_CID_FOCUS_AUTO, 1); - } - if (v4l2_has_control(fd, V4L2_CID_AUTO_FOCUS_START)) { - current.has_af_s = 1; - } +// // Trigger continuous auto focus if the sensor supports it +// if (v4l2_has_control(fd, V4L2_CID_FOCUS_AUTO)) { +// current.has_af_c = 1; +// v4l2_ctrl_set(fd, V4L2_CID_FOCUS_AUTO, 1); +// } +// if (v4l2_has_control(fd, V4L2_CID_AUTO_FOCUS_START)) { +// current.has_af_s = 1; +// } - if (v4l2_has_control(fd, V4L2_CID_GAIN)) { - current.gain_ctrl = V4L2_CID_GAIN; - current.gain_max = v4l2_ctrl_get_max(fd, V4L2_CID_GAIN); - } +// if (v4l2_has_control(fd, V4L2_CID_GAIN)) { +// current.gain_ctrl = V4L2_CID_GAIN; +// current.gain_max = v4l2_ctrl_get_max(fd, V4L2_CID_GAIN); +// } - if (v4l2_has_control(fd, V4L2_CID_ANALOGUE_GAIN)) { - current.gain_ctrl = V4L2_CID_ANALOGUE_GAIN; - current.gain_max = v4l2_ctrl_get_max(fd, V4L2_CID_ANALOGUE_GAIN); - } +// if (v4l2_has_control(fd, V4L2_CID_ANALOGUE_GAIN)) { +// current.gain_ctrl = V4L2_CID_ANALOGUE_GAIN; +// current.gain_max = v4l2_ctrl_get_max(fd, V4L2_CID_ANALOGUE_GAIN); +// } - auto_exposure = 1; - auto_gain = 1; - draw_controls(); +// auto_exposure = 1; +// auto_gain = 1; +// draw_controls(); - close(current.fd); - current.fd = fd; -} +// close(current.fd); +// current.fd = fd; +// } -int -find_dev_node(int maj, int min, char *fnbuf) -{ - DIR *d; - struct dirent *dir; - struct stat info; - d = opendir("/dev"); - while ((dir = readdir(d)) != NULL) { - sprintf(fnbuf, "/dev/%s", dir->d_name); - stat(fnbuf, &info); - if (!S_ISCHR(info.st_mode)) { - continue; - } - if (major(info.st_rdev) == maj && minor(info.st_rdev) == min) { - return 0; - } - } - return -1; -} +// static void +// init_media_entity(char *fn, int width, int height, int mbus) +// { +// int fd; +// struct v4l2_subdev_format fmt = {}; -static void -init_media_entity(char *fn, int width, int height, int mbus) -{ - int fd; - struct v4l2_subdev_format fmt = {}; +// fd = open(fn, O_RDWR); - fd = open(fn, O_RDWR); +// // Apply mode to v4l2 subdev +// g_print("Setting node to %dx%d fmt %d\n", +// width, height, mbus); +// fmt.pad = 0; +// fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; +// fmt.format.code = mbus; +// fmt.format.width = width; +// fmt.format.height = height; +// fmt.format.field = V4L2_FIELD_ANY; - // Apply mode to v4l2 subdev - g_print("Setting node to %dx%d fmt %d\n", - width, height, mbus); - fmt.pad = 0; - fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; - fmt.format.code = mbus; - fmt.format.width = width; - fmt.format.height = height; - fmt.format.field = V4L2_FIELD_ANY; +// if (xioctl(fd, VIDIOC_SUBDEV_S_FMT, &fmt) == -1) { +// errno_exit("VIDIOC_SUBDEV_S_FMT"); +// } +// if (fmt.format.width != width || fmt.format.height != height || fmt.format.code != mbus) +// g_printerr("Driver chose %dx%d fmt %d instead\n", +// fmt.format.width, fmt.format.height, +// fmt.format.code); +// } - if (xioctl(fd, VIDIOC_SUBDEV_S_FMT, &fmt) == -1) { - errno_exit("VIDIOC_SUBDEV_S_FMT"); - } - if (fmt.format.width != width || fmt.format.height != height || fmt.format.code != mbus) - g_printerr("Driver chose %dx%d fmt %d instead\n", - fmt.format.width, fmt.format.height, - fmt.format.code); -} +// static int +// init_device(int fd) +// { +// struct v4l2_capability cap; +// if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) { +// if (errno == EINVAL) { +// fprintf(stderr, "%s is no V4L2 device\n", +// current.dev_name); +// exit(EXIT_FAILURE); +// } else { +// errno_exit("VIDIOC_QUERYCAP"); +// } +// } -static int -init_device(int fd) -{ - struct v4l2_capability cap; - if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) { - if (errno == EINVAL) { - fprintf(stderr, "%s is no V4L2 device\n", - current.dev_name); - exit(EXIT_FAILURE); - } else { - errno_exit("VIDIOC_QUERYCAP"); - } - } +// // Detect buffer format for the interface node, preferring normal video capture +// if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) { +// current.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +// } else if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) { +// printf("[%s] Using the MPLANE buffer format\n", current.cfg_name); +// current.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; +// } else { +// fprintf(stderr, "%s is no video capture device\n", +// current.dev_name); +// exit(EXIT_FAILURE); +// } - // Detect buffer format for the interface node, preferring normal video capture - if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) { - current.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - } else if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) { - printf("[%s] Using the MPLANE buffer format\n", current.cfg_name); - current.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - } else { - fprintf(stderr, "%s is no video capture device\n", - current.dev_name); - exit(EXIT_FAILURE); - } +// if (!(cap.capabilities & V4L2_CAP_STREAMING)) { +// fprintf(stderr, "%s does not support streaming i/o\n", +// current.dev_name); +// exit(EXIT_FAILURE); +// } - if (!(cap.capabilities & V4L2_CAP_STREAMING)) { - fprintf(stderr, "%s does not support streaming i/o\n", - current.dev_name); - exit(EXIT_FAILURE); - } +// /* Select video input, video standard and tune here. */ +// struct v4l2_cropcap cropcap = { +// .type = current.type, +// }; - /* Select video input, video standard and tune here. */ - struct v4l2_cropcap cropcap = { - .type = current.type, - }; +// struct v4l2_crop crop = {0}; +// if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) { +// crop.type = current.type; +// crop.c = cropcap.defrect; /* reset to default */ - struct v4l2_crop crop = {0}; - if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) { - crop.type = current.type; - crop.c = cropcap.defrect; /* reset to default */ +// if (xioctl(fd, VIDIOC_S_CROP, &crop) == -1) { +// switch (errno) { +// case EINVAL: +// /* Cropping not supported. */ +// break; +// default: +// /* Errors ignored. */ +// break; +// } +// } +// } else { +// /* Errors ignored. */ +// } - if (xioctl(fd, VIDIOC_S_CROP, &crop) == -1) { - switch (errno) { - case EINVAL: - /* Cropping not supported. */ - break; - default: - /* Errors ignored. */ - break; - } - } - } else { - /* Errors ignored. */ - } +// // Request a video format +// struct v4l2_format fmt = { +// .type = current.type, +// }; +// if (current.width > 0) { +// g_print("Setting camera to %dx%d fmt %d\n", +// current.width, current.height, current.fmt); - // Request a video format - struct v4l2_format fmt = { - .type = current.type, - }; - if (current.width > 0) { - g_print("Setting camera to %dx%d fmt %d\n", - current.width, current.height, current.fmt); +// if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { +// fmt.fmt.pix_mp.width = current.width; +// fmt.fmt.pix_mp.height = current.height; +// fmt.fmt.pix_mp.pixelformat = current.fmt; +// fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; +// } else { +// fmt.fmt.pix.width = current.width; +// fmt.fmt.pix.height = current.height; +// fmt.fmt.pix.pixelformat = current.fmt; +// fmt.fmt.pix.field = V4L2_FIELD_ANY; +// } - if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - fmt.fmt.pix_mp.width = current.width; - fmt.fmt.pix_mp.height = current.height; - fmt.fmt.pix_mp.pixelformat = current.fmt; - fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; - } else { - fmt.fmt.pix.width = current.width; - fmt.fmt.pix.height = current.height; - fmt.fmt.pix.pixelformat = current.fmt; - fmt.fmt.pix.field = V4L2_FIELD_ANY; - } +// if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) { +// g_printerr("VIDIOC_S_FMT failed"); +// show_error("Could not set camera mode"); +// return -1; +// } - if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) { - g_printerr("VIDIOC_S_FMT failed"); - show_error("Could not set camera mode"); - return -1; - } - - if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE - && (fmt.fmt.pix_mp.width != current.width || - fmt.fmt.pix_mp.height != current.height || - fmt.fmt.pix_mp.pixelformat != current.fmt)) - g_printerr("Driver returned %dx%d fmt %d\n", - fmt.fmt.pix_mp.width, fmt.fmt.pix_mp.height, - fmt.fmt.pix_mp.pixelformat); - if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE - && (fmt.fmt.pix.width != current.width || - fmt.fmt.pix.height != current.height || - fmt.fmt.pix.pixelformat != current.fmt)) - g_printerr("Driver returned %dx%d fmt %d\n", - fmt.fmt.pix.width, fmt.fmt.pix.height, - fmt.fmt.pix.pixelformat); +// if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE +// && (fmt.fmt.pix_mp.width != current.width || +// fmt.fmt.pix_mp.height != current.height || +// fmt.fmt.pix_mp.pixelformat != current.fmt)) +// g_printerr("Driver returned %dx%d fmt %d\n", +// fmt.fmt.pix_mp.width, fmt.fmt.pix_mp.height, +// fmt.fmt.pix_mp.pixelformat); +// if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE +// && (fmt.fmt.pix.width != current.width || +// fmt.fmt.pix.height != current.height || +// fmt.fmt.pix.pixelformat != current.fmt)) +// g_printerr("Driver returned %dx%d fmt %d\n", +// fmt.fmt.pix.width, fmt.fmt.pix.height, +// fmt.fmt.pix.pixelformat); - /* Note VIDIOC_S_FMT may change width and height. */ - } else { - if (xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) { - errno_exit("VIDIOC_G_FMT"); - } - if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - g_print("Got %dx%d fmt %d from the driver\n", - fmt.fmt.pix_mp.width, fmt.fmt.pix_mp.height, - fmt.fmt.pix_mp.pixelformat); - current.width = fmt.fmt.pix.width; - current.height = fmt.fmt.pix.height; - } else { - g_print("Got %dx%d fmt %d from the driver\n", - fmt.fmt.pix.width, fmt.fmt.pix.height, - fmt.fmt.pix.pixelformat); - current.width = fmt.fmt.pix_mp.width; - current.height = fmt.fmt.pix_mp.height; - } - } - if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - current.fmt = fmt.fmt.pix_mp.pixelformat; - } else { - current.fmt = fmt.fmt.pix.pixelformat; - } +// /* Note VIDIOC_S_FMT may change width and height. */ +// } else { +// if (xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) { +// errno_exit("VIDIOC_G_FMT"); +// } +// if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { +// g_print("Got %dx%d fmt %d from the driver\n", +// fmt.fmt.pix_mp.width, fmt.fmt.pix_mp.height, +// fmt.fmt.pix_mp.pixelformat); +// current.width = fmt.fmt.pix.width; +// current.height = fmt.fmt.pix.height; +// } else { +// g_print("Got %dx%d fmt %d from the driver\n", +// fmt.fmt.pix.width, fmt.fmt.pix.height, +// fmt.fmt.pix.pixelformat); +// current.width = fmt.fmt.pix_mp.width; +// current.height = fmt.fmt.pix_mp.height; +// } +// } +// if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { +// current.fmt = fmt.fmt.pix_mp.pixelformat; +// } else { +// current.fmt = fmt.fmt.pix.pixelformat; +// } - init_mmap(fd); - return 0; -} - -static void -register_custom_tiff_tags(TIFF *tif) -{ - static const TIFFFieldInfo custom_fields[] = { - {TIFFTAG_FORWARDMATRIX1, -1, -1, TIFF_SRATIONAL, FIELD_CUSTOM, 1, 1, "ForwardMatrix1"}, - }; - - // Add missing dng fields - TIFFMergeFieldInfo(tif, custom_fields, sizeof(custom_fields) / sizeof(custom_fields[0])); -} - -static void -process_image(const int *p, int size) -{ - time_t rawtime; - char datetime[20] = {0}; - struct tm tim; - uint8_t *pixels; - char fname[255]; - char fname_target[255]; - char command[1024]; - char timestamp[30]; - char uniquecameramodel[255]; - GdkPixbuf *pixbuf; - GdkPixbuf *pixbufrot; - GdkPixbuf *thumb; - GError *error = NULL; - double scale; - cairo_t *cr; - TIFF *tif; - int skip = 2; - long sub_offset = 0; - uint64 exif_offset = 0; - static const short cfapatterndim[] = {2, 2}; - static const float neutral[] = {1.0, 1.0, 1.0}; - static uint16_t isospeed[] = {0}; - - // Only process preview frames when not capturing - if (capture == 0) { - if(current.width > 1281) { - skip = 2; - } - if(current.width > 1920) { - skip = 3; - } - pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, current.width / (skip*2), current.height / (skip*2)); - pixels = gdk_pixbuf_get_pixels(pixbuf); - - switch(current.fmt) { - case V4L2_PIX_FMT_SBGGR8: - case V4L2_PIX_FMT_SGBRG8: - case V4L2_PIX_FMT_SGRBG8: - case V4L2_PIX_FMT_SRGGB8: - case V4L2_PIX_FMT_SRGGB10P: - quick_debayer((const uint8_t *)p, pixels, current.fmt, - current.width, current.height, skip, - current.blacklevel); - break; - case V4L2_PIX_FMT_UYVY: - case V4L2_PIX_FMT_YUYV: - quick_yuv2rgb((const uint8_t *)p, pixels, current.fmt, - current.width, current.height, skip); - break; - } - - if (current.rotate == 0) { - pixbufrot = pixbuf; - } else if (current.rotate == 90) { - pixbufrot = gdk_pixbuf_rotate_simple(pixbuf, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE); - } else if (current.rotate == 180) { - pixbufrot = gdk_pixbuf_rotate_simple(pixbuf, GDK_PIXBUF_ROTATE_UPSIDEDOWN); - } else if (current.rotate == 270) { - pixbufrot = gdk_pixbuf_rotate_simple(pixbuf, GDK_PIXBUF_ROTATE_CLOCKWISE); - } - - // Draw preview image - scale = (double) preview_width / gdk_pixbuf_get_width(pixbufrot); - cr = cairo_create(surface); - cairo_set_source_rgb(cr, 0, 0, 0); - cairo_paint(cr); - cairo_scale(cr, scale, scale); - gdk_cairo_set_source_pixbuf(cr, pixbufrot, 0, 0); - cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_NONE); - cairo_paint(cr); - - // Draw controls over preview - cairo_set_operator(cr, CAIRO_OPERATOR_OVER); - cairo_set_source_surface(cr, status_surface, 0, 0); - cairo_paint(cr); - - // Queue gtk3 repaint of the preview area - gtk_widget_queue_draw_area(preview, 0, 0, preview_width, preview_height); - cairo_destroy(cr); - g_object_unref(pixbufrot); - g_object_unref(pixbuf); - } else { - capture--; - time(&rawtime); - tim = *(localtime(&rawtime)); - strftime(timestamp, 30, "%Y%m%d%H%M%S", &tim); - strftime(datetime, 20, "%Y:%m:%d %H:%M:%S", &tim); - - sprintf(fname_target, "%s/Pictures/IMG%s", getenv("HOME"), timestamp); - sprintf(fname, "%s/%d.dng", burst_dir, burst_length - capture); - - // Get latest exposure and gain now the auto gain/exposure is disabled while capturing - gain = v4l2_ctrl_get(current.fd, current.gain_ctrl); - exposure = v4l2_ctrl_get(current.fd, V4L2_CID_EXPOSURE); - - if(!(tif = TIFFOpen(fname, "w"))) { - printf("Could not open tiff\n"); - } - - // Define TIFF thumbnail - TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 1); - TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, current.width >> 4); - TIFFSetField(tif, TIFFTAG_IMAGELENGTH, current.height >> 4); - TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); - TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); - TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); - TIFFSetField(tif, TIFFTAG_MAKE, exif_make); - TIFFSetField(tif, TIFFTAG_MODEL, exif_model); - TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); - TIFFSetField(tif, TIFFTAG_DATETIME, datetime); - TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); - TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); - TIFFSetField(tif, TIFFTAG_SOFTWARE, "Megapixels"); - TIFFSetField(tif, TIFFTAG_SUBIFD, 1, &sub_offset); - TIFFSetField(tif, TIFFTAG_DNGVERSION, "\001\001\0\0"); - TIFFSetField(tif, TIFFTAG_DNGBACKWARDVERSION, "\001\0\0\0"); - sprintf(uniquecameramodel, "%s %s", exif_make, exif_model); - TIFFSetField(tif, TIFFTAG_UNIQUECAMERAMODEL, uniquecameramodel); - if(current.colormatrix[0]) { - TIFFSetField(tif, TIFFTAG_COLORMATRIX1, 9, current.colormatrix); - } else { - TIFFSetField(tif, TIFFTAG_COLORMATRIX1, 9, colormatrix_srgb); - } - if(current.forwardmatrix[0]) { - TIFFSetField(tif, TIFFTAG_FORWARDMATRIX1, 9, current.forwardmatrix); - } - TIFFSetField(tif, TIFFTAG_ASSHOTNEUTRAL, 3, neutral); - TIFFSetField(tif, TIFFTAG_CALIBRATIONILLUMINANT1, 21); - // Write black thumbnail, only windows uses this - { - unsigned char *buf = (unsigned char *)calloc(1, (int)current.width >> 4); - for (int row = 0; row < current.height>>4; row++) { - TIFFWriteScanline(tif, buf, row, 0); - } - free(buf); - } - TIFFWriteDirectory(tif); - - // Define main photo - TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 0); - TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, current.width); - TIFFSetField(tif, TIFFTAG_IMAGELENGTH, current.height); - TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); - TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA); - TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); - TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); - TIFFSetField(tif, TIFFTAG_CFAREPEATPATTERNDIM, cfapatterndim); - TIFFSetField(tif, TIFFTAG_CFAPATTERN, "\002\001\001\000"); // BGGR - if(current.whitelevel) { - TIFFSetField(tif, TIFFTAG_WHITELEVEL, 1, ¤t.whitelevel); - } - if(current.blacklevel) { - TIFFSetField(tif, TIFFTAG_BLACKLEVEL, 1, ¤t.blacklevel); - } - TIFFCheckpointDirectory(tif); - printf("Writing frame to %s\n", fname); - - unsigned char *pLine = (unsigned char*)malloc(current.width); - for(int row = 0; row < current.height; row++){ - TIFFWriteScanline(tif, ((uint8_t *)p)+(row*current.width), row, 0); - } - free(pLine); - TIFFWriteDirectory(tif); - - // Add an EXIF block to the tiff - TIFFCreateEXIFDirectory(tif); - // 1 = manual, 2 = full auto, 3 = aperture priority, 4 = shutter priority - if (auto_exposure) { - TIFFSetField(tif, EXIFTAG_EXPOSUREPROGRAM, 2); - } else { - TIFFSetField(tif, EXIFTAG_EXPOSUREPROGRAM, 1); - } - - TIFFSetField(tif, EXIFTAG_EXPOSURETIME, (1.0/current.rate) / ((float)current.height / (float)exposure)); - isospeed[0] = (uint16_t)remap(gain - 1, 0, current.gain_max, current.iso_min, current.iso_max); - TIFFSetField(tif, EXIFTAG_ISOSPEEDRATINGS, 1, isospeed); - TIFFSetField(tif, EXIFTAG_FLASH, 0); - - TIFFSetField(tif, EXIFTAG_DATETIMEORIGINAL, datetime); - TIFFSetField(tif, EXIFTAG_DATETIMEDIGITIZED, datetime); - if(current.fnumber) { - TIFFSetField(tif, EXIFTAG_FNUMBER, current.fnumber); - } - if(current.focallength) { - TIFFSetField(tif, EXIFTAG_FOCALLENGTH, current.focallength); - } - if(current.focallength && current.cropfactor) { - TIFFSetField(tif, EXIFTAG_FOCALLENGTHIN35MMFILM, (short)(current.focallength * current.cropfactor)); - } - TIFFWriteCustomDirectory(tif, &exif_offset); - TIFFFreeDirectory(tif); - - // Update exif pointer - TIFFSetDirectory(tif, 0); - TIFFSetField(tif, TIFFTAG_EXIFIFD, exif_offset); - TIFFRewriteDirectory(tif); - - TIFFClose(tif); - - - if (capture == 0) { - // Update the thumbnail if this is the last frame - pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, current.width / (skip*2), current.height / (skip*2)); - pixels = gdk_pixbuf_get_pixels(pixbuf); - quick_debayer((const uint8_t *)p, pixels, current.fmt, - current.width, current.height, skip, - current.blacklevel); - - if (current.rotate == 0) { - pixbufrot = pixbuf; - } else if (current.rotate == 90) { - pixbufrot = gdk_pixbuf_rotate_simple(pixbuf, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE); - } else if (current.rotate == 180) { - pixbufrot = gdk_pixbuf_rotate_simple(pixbuf, GDK_PIXBUF_ROTATE_UPSIDEDOWN); - } else if (current.rotate == 270) { - pixbufrot = gdk_pixbuf_rotate_simple(pixbuf, GDK_PIXBUF_ROTATE_CLOCKWISE); - } - thumb = gdk_pixbuf_scale_simple(pixbufrot, 24, 24, GDK_INTERP_BILINEAR); - gtk_image_set_from_pixbuf(GTK_IMAGE(thumb_last), thumb); - sprintf(last_path, "%s.jpg", fname_target); - if (error != NULL) { - g_printerr("%s\n", error->message); - g_clear_error(&error); - } - - g_object_unref(pixbufrot); - g_object_unref(pixbuf); - - // Start post-processing the captured burst - g_print("Post process %s to %s.ext\n", burst_dir, fname_target); - sprintf(command, "%s %s %s &", processing_script, burst_dir, fname_target); - system(command); - - // Restore the auto exposure and gain if needed - if (auto_exposure) { - v4l2_ctrl_set(current.fd, V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_AUTO); - } - if (auto_gain) { - v4l2_ctrl_set(current.fd, V4L2_CID_AUTOGAIN, 1); - } - - } - } -} +// init_mmap(fd); +// return 0; +// } static gboolean preview_draw(GtkWidget *widget, cairo_t *cr, gpointer data) { - cairo_set_source_surface(cr, surface, 0, 0); + if (!camera_is_initialized) { + return FALSE; + } + + if (surface) { + cairo_save(cr); + + cairo_translate(cr, preview_width / 2, preview_height / 2); + + int width = cairo_image_surface_get_width(surface); + int height = cairo_image_surface_get_height(surface); + double scale = MIN(preview_width / (double) width, preview_height / (double) height); + cairo_scale(cr, scale, scale); + + cairo_translate(cr, -width / 2, -height / 2); + + cairo_set_source_surface(cr, surface, 0, 0); + cairo_paint(cr); + cairo_restore(cr); + } + + cairo_set_source_surface(cr, status_surface, 0, 0); cairo_paint(cr); return FALSE; } @@ -949,443 +623,99 @@ preview_draw(GtkWidget *widget, cairo_t *cr, gpointer data) static gboolean preview_configure(GtkWidget *widget, GdkEventConfigure *event) { - cairo_t *cr; + int new_preview_width = gtk_widget_get_allocated_width(widget); + int new_preview_height = gtk_widget_get_allocated_height(widget); - if (surface) - cairo_surface_destroy(surface); - - surface = gdk_window_create_similar_surface(gtk_widget_get_window(widget), - CAIRO_CONTENT_COLOR, - gtk_widget_get_allocated_width(widget), - gtk_widget_get_allocated_height(widget)); - - preview_width = gtk_widget_get_allocated_width(widget); - preview_height = gtk_widget_get_allocated_height(widget); - - cr = cairo_create(surface); - cairo_set_source_rgb(cr, 0, 0, 0); - cairo_paint(cr); - cairo_destroy(cr); + if (preview_width != new_preview_width || preview_height != new_preview_height) + { + preview_width = new_preview_width; + preview_height = new_preview_height; + update_io_pipeline(); + } draw_controls(); return TRUE; } -static int -read_frame(int fd) -{ - int bytesused; - - struct v4l2_buffer buf = { - .type = current.type, - .memory = V4L2_MEMORY_MMAP, - .index = n_buffers, - }; - - if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - buf.m.planes = buf_planes; - buf.length = 1; - } - - if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) { - switch (errno) { - case EAGAIN: - return 0; - case EIO: - /* Could ignore EIO, see spec. */ - /* fallthrough */ - default: - errno_exit("VIDIOC_DQBUF"); - break; - } - } - - if (current.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - bytesused = buf.m.planes[0].bytesused; - } else { - bytesused = buf.bytesused; - } - - process_image(buffers[buf.index].start, bytesused); - - if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { - errno_exit("VIDIOC_QBUF"); - } - - return 1; -} - -gboolean -get_frame() -{ - if (ready == 0) - return TRUE; - while (1) { - fd_set fds; - struct timeval tv; - int r; - - FD_ZERO(&fds); - FD_SET(current.video_fd, &fds); - - /* Timeout. */ - tv.tv_sec = 2; - tv.tv_usec = 0; - - r = select(current.video_fd + 1, &fds, NULL, NULL, &tv); - - if (r == -1) { - if (EINTR == errno) { - continue; - } - errno_exit("select"); - } else if (r == 0) { - g_printerr("get_frame: select timeout\n"); - return TRUE; - } - - if (read_frame(current.video_fd)) { - break; - } - /* EAGAIN - continue select loop. */ - } - return TRUE; -} - -int -strtoint(const char *nptr, char **endptr, int base) -{ - long x = strtol(nptr, endptr, base); - assert(x <= INT_MAX); - return (int) x; -} - -static int -config_ini_handler(void *user, const char *section, const char *name, - const char *value) -{ - struct camerainfo *cc; - int cid; - int found; - int first_free; - int i; - - if (strcmp(section, "device") == 0) { - if (strcmp(name, "make") == 0) { - exif_make = strdup(value); - } else if (strcmp(name, "model") == 0) { - exif_model = strdup(value); - } else { - g_printerr("Unknown key '%s' in [device]\n", name); - exit(1); - } - } else { - found = 0; - first_free = -1; - for (int i=0; iwidth = strtoint(value, NULL, 10); - } else if (strcmp(name, "height") == 0) { - cc->height = strtoint(value, NULL, 10); - } else if (strcmp(name, "rate") == 0) { - cc->rate = strtoint(value, NULL, 10); - } else if (strcmp(name, "rotate") == 0) { - cc->rotate = strtoint(value, NULL, 10); - } else if (strcmp(name, "fmt") == 0) { - if (strcmp(value, "RGGB8") == 0) { - cc->fmt = V4L2_PIX_FMT_SRGGB8; - cc->mbus = MEDIA_BUS_FMT_SRGGB8_1X8; - } else if (strcmp(value, "BGGR8") == 0) { - cc->fmt = V4L2_PIX_FMT_SBGGR8; - cc->mbus = MEDIA_BUS_FMT_SBGGR8_1X8; - } else if (strcmp(value, "GRBG8") == 0) { - cc->fmt = V4L2_PIX_FMT_SGRBG8; - cc->mbus = MEDIA_BUS_FMT_SGRBG8_1X8; - } else if (strcmp(value, "GBRG8") == 0) { - cc->fmt = V4L2_PIX_FMT_SGBRG8; - cc->mbus = MEDIA_BUS_FMT_SGBRG8_1X8; - } else if (strcmp(value, "RGGB10P") == 0) { - cc->fmt = V4L2_PIX_FMT_SRGGB10P; - cc->mbus = MEDIA_BUS_FMT_SRGGB10_1X10; - } else if (strcmp(value, "UYVY") == 0) { - cc->fmt = V4L2_PIX_FMT_UYVY; - cc->mbus = MEDIA_BUS_FMT_UYVY8_2X8; - } else { - g_printerr("Unsupported pixelformat %s\n", value); - exit(1); - } - } else if (strcmp(name, "driver") == 0) { - strcpy(cc->dev_name, value); - } else if (strcmp(name, "media-driver") == 0) { - strcpy(cc->media_dev_name, value); - } else if (strcmp(name, "media-links") == 0) { - char **linkdefs; - linkdefs = g_strsplit(value, ",", 0); - i = 0; - while (linkdefs[i] != NULL) { - char **linkdef = g_strsplit(linkdefs[i], "->", 2); - char **porta = g_strsplit(linkdef[0], ":", 2); - char **portb = g_strsplit(linkdef[1], ":", 2); - - cc->media_links[i].valid = 1; - strcpy(cc->media_links[i].source_name, porta[0]); - strcpy(cc->media_links[i].target_name, portb[0]); - cc->media_links[i].source_port = strtoint(porta[1], NULL, 10); - cc->media_links[i].target_port = strtoint(portb[1], NULL, 10); - - g_strfreev(portb); - g_strfreev(porta); - g_strfreev(linkdef); - i++; - } - g_strfreev(linkdefs); - } else if (strcmp(name, "colormatrix") == 0) { - sscanf(value, "%f,%f,%f,%f,%f,%f,%f,%f,%f", - cc->colormatrix+0, - cc->colormatrix+1, - cc->colormatrix+2, - cc->colormatrix+3, - cc->colormatrix+4, - cc->colormatrix+5, - cc->colormatrix+6, - cc->colormatrix+7, - cc->colormatrix+8 - ); - } else if (strcmp(name, "forwardmatrix") == 0) { - sscanf(value, "%f,%f,%f,%f,%f,%f,%f,%f,%f", - cc->forwardmatrix+0, - cc->forwardmatrix+1, - cc->forwardmatrix+2, - cc->forwardmatrix+3, - cc->forwardmatrix+4, - cc->forwardmatrix+5, - cc->forwardmatrix+6, - cc->forwardmatrix+7, - cc->forwardmatrix+8 - ); - } else if (strcmp(name, "whitelevel") == 0) { - cc->whitelevel = strtoint(value, NULL, 10); - } else if (strcmp(name, "blacklevel") == 0) { - cc->blacklevel = strtoint(value, NULL, 10); - } else if (strcmp(name, "focallength") == 0) { - cc->focallength = strtof(value, NULL); - } else if (strcmp(name, "cropfactor") == 0) { - cc->cropfactor = strtof(value, NULL); - } else if (strcmp(name, "fnumber") == 0) { - cc->fnumber = strtod(value, NULL); - } else if (strcmp(name, "iso-min") == 0) { - cc->iso_min = strtod(value, NULL); - } else if (strcmp(name, "iso-max") == 0) { - cc->iso_max = strtod(value, NULL); - } else { - g_printerr("Unknown key '%s' in [%s]\n", name, section); - exit(1); - } - } - return 1; -} - -int -setup_camera(int cid) -{ - struct media_link_desc link = {0}; +// int +// setup_camera(int cid) +// { +// struct media_link_desc link = {0}; - // Kill existing links for cameras in the same graph - for(int i=0; ifront link - link.flags = 0; - link.source.entity = cameras[i].entity_id; - link.source.index = 0; - link.sink.entity = cameras[i].interface_entity_id; - link.sink.index = 0; +// // Disable the interface<->front link +// link.flags = 0; +// link.source.entity = cameras[i].entity_id; +// link.source.index = 0; +// link.sink.entity = cameras[i].interface_entity_id; +// link.sink.index = 0; - if (xioctl(cameras[cid].media_fd, MEDIA_IOC_SETUP_LINK, &link) < 0) { - g_printerr("Could not disable [%s] camera link\n", cameras[i].cfg_name); - return -1; - } - } +// if (xioctl(cameras[cid].media_fd, MEDIA_IOC_SETUP_LINK, &link) < 0) { +// g_printerr("Could not disable [%s] camera link\n", cameras[i].cfg_name); +// return -1; +// } +// } - // Enable the interface<->sensor link - link.flags = MEDIA_LNK_FL_ENABLED; - link.source.entity = cameras[cid].entity_id; - link.source.index = 0; - link.sink.entity = cameras[cid].interface_entity_id; - link.sink.index = 0; +// // Enable the interface<->sensor link +// link.flags = MEDIA_LNK_FL_ENABLED; +// link.source.entity = cameras[cid].entity_id; +// link.source.index = 0; +// link.sink.entity = cameras[cid].interface_entity_id; +// link.sink.index = 0; - current = cameras[cid]; +// current = cameras[cid]; - if (xioctl(cameras[cid].media_fd, MEDIA_IOC_SETUP_LINK, &link) < 0) { - g_printerr("[%s] Could not enable direct sensor->if link\n", cameras[cid].cfg_name); +// if (xioctl(cameras[cid].media_fd, MEDIA_IOC_SETUP_LINK, &link) < 0) { +// g_printerr("[%s] Could not enable direct sensor->if link\n", cameras[cid].cfg_name); - for(int i=0;i [%s:%d]\n", - cameras[cid].cfg_name, - cameras[cid].media_links[i].source_name, - cameras[cid].media_links[i].source_port, - cameras[cid].media_links[i].target_name, - cameras[cid].media_links[i].target_port); +// link.flags = MEDIA_LNK_FL_ENABLED; +// link.source.entity = cameras[cid].media_links[i].source_entity_id; +// link.source.index = cameras[cid].media_links[i].source_port; +// link.sink.entity = cameras[cid].media_links[i].target_entity_id; +// link.sink.index = cameras[cid].media_links[i].target_port; +// if (xioctl(cameras[cid].media_fd, MEDIA_IOC_SETUP_LINK, &link) < 0) { +// g_printerr("[%s] Could not link [%s:%d] -> [%s:%d]\n", +// cameras[cid].cfg_name, +// cameras[cid].media_links[i].source_name, +// cameras[cid].media_links[i].source_port, +// cameras[cid].media_links[i].target_name, +// cameras[cid].media_links[i].target_port); - } - init_media_entity(cameras[cid].media_links[i].source_fname, current.width, current.height, current.mbus); - init_media_entity(cameras[cid].media_links[i].target_fname, current.width, current.height, current.mbus); - } - } +// } +// init_media_entity(cameras[cid].media_links[i].source_fname, current.width, current.height, current.mbus); +// init_media_entity(cameras[cid].media_links[i].target_fname, current.width, current.height, current.mbus); +// } +// } - // Find camera node - init_sensor(current.dev_fname, current.width, current.height, current.mbus, current.rate); - return 0; -} +// // Find camera node +// init_sensor(current.dev_fname, current.width, current.height, current.mbus, current.rate); +// return 0; +// } -int -find_camera(int cid) -{ - struct media_entity_desc entity = {0}; - DIR *d; - struct dirent *dir; - int fd; - char fnbuf[261]; - struct media_device_info mdi = {0}; - int ret; - int found_subdev = 0; - int found_interface = 0; - - // find the /dev/media node for the camera media-driver - d = opendir("/dev"); - while ((dir = readdir(d)) != NULL) { - if (strncmp(dir->d_name, "media", 5) == 0) { - sprintf(fnbuf, "/dev/%s", dir->d_name); - fd = open(fnbuf, O_RDWR); - xioctl(fd, MEDIA_IOC_DEVICE_INFO, &mdi); - if (strcmp(mdi.driver, cameras[cid].media_dev_name) == 0) { - printf("[%s] media device: %s (%s)\n", cameras[cid].cfg_name, fnbuf, mdi.driver); - cameras[cid].media_fd = fd; - goto find_camera_found_media; - } - close(fd); - } - } - g_printerr("Could not find /dev/media* node matching '%s'\n", cameras[cid].media_dev_name); - return 0; - -find_camera_found_media: - // inspect the media node and find the sensor - while (1) { - entity.id = entity.id | MEDIA_ENT_ID_FLAG_NEXT; - ret = xioctl(fd, MEDIA_IOC_ENUM_ENTITIES, &entity); - if (ret < 0) { - break; - } - if (!found_subdev && strncmp(entity.name, cameras[cid].dev_name, strlen(cameras[cid].dev_name)) == 0) { - cameras[cid].entity_id = entity.id; - find_dev_node(entity.dev.major, entity.dev.minor, cameras[cid].dev_fname); - printf("[%s] subdev: %s (%s)\n", cameras[cid].cfg_name, cameras[cid].dev_fname, entity.name); - found_subdev = 1; - } - if (!found_interface && entity.type == MEDIA_ENT_F_IO_V4L) { - cameras[cid].interface_entity_id = entity.id; - find_dev_node(entity.dev.major, entity.dev.minor, cameras[cid].video_dev_fname); - printf("[%s] video: %s (%s)\n", cameras[cid].cfg_name, cameras[cid].video_dev_fname, entity.name); - found_interface = 1; - } - - for (int i=0; ix > 60 && event->x < 120) { // Shutter angle current_control = USER_CONTROL_SHUTTER; gtk_label_set_text(GTK_LABEL(control_name), "Shutter"); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto), auto_exposure); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto), !exposure_is_manual); gtk_adjustment_set_lower(control_slider, 1.0); gtk_adjustment_set_upper(control_slider, 360.0); gtk_adjustment_set_value(control_slider, (double)exposure); @@ -1472,9 +787,8 @@ on_preview_tap(GtkWidget *widget, GdkEventButton *event, gpointer user_data) } // Tapped preview image itself, try focussing - if (current.has_af_s) { - v4l2_ctrl_set(current.fd, V4L2_CID_AUTO_FOCUS_STOP, 1); - v4l2_ctrl_set(current.fd, V4L2_CID_AUTO_FOCUS_START, 1); + if (has_auto_focus_start) { + mp_io_pipeline_focus(); } } @@ -1487,49 +801,16 @@ on_error_close_clicked(GtkWidget *widget, gpointer user_data) void on_camera_switch_clicked(GtkWidget *widget, gpointer user_data) { - int found_next = 0; - int old_cid = current_cid; - int next_cid = -1; - for(int i=current_cid; iindex + 1; + const struct mp_camera_config *next_camera = mp_get_camera_config(next_index); - found_next = 1; - next_cid = i; - } - if(!found_next) { - for(int i=0; icapture_mode.height); + if (new_exposure != exposure) { + exposure = new_exposure; + has_changed = true; + } break; - } - draw_controls(); -} - -int -find_config(char *conffile) -{ - char buf[512]; - char *xdg_config_home; - wordexp_t exp_result; - FILE *fp; - - // Resolve XDG stuff - if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) { - xdg_config_home = "~/.config"; - } - wordexp(xdg_config_home, &exp_result, 0); - xdg_config_home = strdup(exp_result.we_wordv[0]); - wordfree(&exp_result); - - if(access("/proc/device-tree/compatible", F_OK) != -1) { - // Reads to compatible string of the current device tree, looks like: - // pine64,pinephone-1.2\0allwinner,sun50i-a64\0 - fp = fopen("/proc/device-tree/compatible", "r"); - fgets(buf, 512, fp); - fclose(fp); - - // Check config/%dt.ini in the current working directory - sprintf(conffile, "config/%s.ini", buf); - if(access(conffile, F_OK) != -1) { - printf("Found config file at %s\n", conffile); - return 0; } - - // Check for a config file in XDG_CONFIG_HOME - sprintf(conffile, "%s/megapixels/config/%s.ini", xdg_config_home, buf); - if(access(conffile, F_OK) != -1) { - printf("Found config file at %s\n", conffile); - return 0; - } - - // Check user overridden /etc/megapixels/config/$dt.ini - sprintf(conffile, "%s/megapixels/config/%s.ini", SYSCONFDIR, buf); - if(access(conffile, F_OK) != -1) { - printf("Found config file at %s\n", conffile); - return 0; - } - // Check packaged /usr/share/megapixels/config/$dt.ini - sprintf(conffile, "%s/megapixels/config/%s.ini", DATADIR, buf); - if(access(conffile, F_OK) != -1) { - printf("Found config file at %s\n", conffile); - return 0; - } - printf("%s not found\n", conffile); - } else { - printf("Could not read device name from device tree\n"); } - // If all else fails, fall back to /etc/megapixels.ini - sprintf(conffile, "/etc/megapixels.ini"); - if(access(conffile, F_OK) != -1) { - printf("Found config file at %s\n", conffile); - return 0; + if (has_changed) { + update_io_pipeline(); + draw_controls(); } - return -1; -} - -int -find_processor(char *script) -{ - char *xdg_config_home; - char filename[] = "postprocess.sh"; - wordexp_t exp_result; - - // Resolve XDG stuff - if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) { - xdg_config_home = "~/.config"; - } - wordexp(xdg_config_home, &exp_result, 0); - xdg_config_home = strdup(exp_result.we_wordv[0]); - wordfree(&exp_result); - - // Check postprocess.h in the current working directory - sprintf(script, "%s", filename); - if(access(script, F_OK) != -1) { - sprintf(script, "./%s", filename); - printf("Found postprocessor script at %s\n", script); - return 0; - } - - // Check for a script in XDG_CONFIG_HOME - sprintf(script, "%s/megapixels/%s", xdg_config_home, filename); - if(access(script, F_OK) != -1) { - printf("Found postprocessor script at %s\n", script); - return 0; - } - - // Check user overridden /etc/megapixels/postprocessor.sh - sprintf(script, "%s/megapixels/%s", SYSCONFDIR, filename); - if(access(script, F_OK) != -1) { - printf("Found postprocessor script at %s\n", script); - return 0; - } - - // Check packaged /usr/share/megapixels/postprocessor.sh - sprintf(script, "%s/megapixels/%s", DATADIR, filename); - if(access(script, F_OK) != -1) { - printf("Found postprocessor script at %s\n", script); - return 0; - } - - return -1; } int main(int argc, char *argv[]) { - int ret; - char conffile[512]; - - ret = find_config(conffile); - if (ret) { - g_printerr("Could not find any config file\n"); - return ret; - } - ret = find_processor(processing_script); - if (ret) { - g_printerr("Could not find any post-process script\n"); - return ret; - } + if (!mp_load_config()) + return 1; setenv("LC_NUMERIC", "C", 1); - TIFFSetTagExtender(register_custom_tiff_tags); gtk_init(&argc, &argv); g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL); @@ -1775,59 +944,15 @@ main(int argc, char *argv[]) GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_USER); - int result = ini_parse(conffile, config_ini_handler, NULL); - if (result == -1) { - g_printerr("Config file not found\n"); - return 1; - } else if (result == -2) { - g_printerr("Could not allocate memory to parse config file\n"); - return 1; - } else if (result != 0) { - g_printerr("Could not parse config file\n"); - return 1; - } - if (find_cameras() == 0) { - g_printerr("Could not find the cameras\n"); - show_error("Could not find the cameras"); - goto failed; - } + mp_io_pipeline_start(); - // Disable the camera switch button if only one camera exists - int camera_count = 0; - for (int i=0; i +#include +#include +#include +#include + +#define TIFFTAG_FORWARDMATRIX1 50964 + +static const float colormatrix_srgb[] = { + 3.2409, -1.5373, -0.4986, + -0.9692, 1.8759, 0.0415, + 0.0556, -0.2039, 1.0569 +}; + +static MPPipeline *pipeline; + +static char burst_dir[23]; +static char processing_script[512]; + +static volatile bool is_capturing = false; +static volatile int frames_processed = 0; +static volatile int frames_received = 0; + +static const struct mp_camera_config *camera; + +static MPCameraMode mode; + +static int burst_length; +static int captures_remaining = 0; + +static int preview_width; +static int preview_height; + +// static bool gain_is_manual; +static int gain; +static int gain_max; + +static bool exposure_is_manual; +static int exposure; + +static char capture_fname[255]; + +// static void +// process_image(const int *p, int size) +// { +// time_t rawtime; +// char datetime[20] = {0}; +// struct tm tim; +// uint8_t *pixels; +// char fname[255]; +// char fname_target[255]; +// char command[1024]; +// char timestamp[30]; +// char uniquecameramodel[255]; +// GdkPixbuf *pixbuf; +// GdkPixbuf *pixbufrot; +// GdkPixbuf *thumb; +// GError *error = NULL; +// double scale; +// cairo_t *cr; +// TIFF *tif; +// int skip = 2; +// long sub_offset = 0; +// uint64 exif_offset = 0; +// static const short cfapatterndim[] = {2, 2}; +// static const float neutral[] = {1.0, 1.0, 1.0}; +// static uint16_t isospeed[] = {0}; + +// // Only process preview frames when not capturing +// if (capture == 0) { + +// } else { + + +// if (capture == 0) { + + +// // Restore the auto exposure and gain if needed +// // if (auto_exposure) { +// // v4l2_ctrl_set(current.fd, V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_AUTO); +// // } +// // if (auto_gain) { +// // v4l2_ctrl_set(current.fd, V4L2_CID_AUTOGAIN, 1); +// // } +// } +// } +// } + +static void +register_custom_tiff_tags(TIFF *tif) +{ + static const TIFFFieldInfo custom_fields[] = { + {TIFFTAG_FORWARDMATRIX1, -1, -1, TIFF_SRATIONAL, FIELD_CUSTOM, 1, 1, "ForwardMatrix1"}, + }; + + // Add missing dng fields + TIFFMergeFieldInfo(tif, custom_fields, sizeof(custom_fields) / sizeof(custom_fields[0])); +} + +static bool +find_processor(char *script) +{ + char *xdg_config_home; + char filename[] = "postprocess.sh"; + wordexp_t exp_result; + + // Resolve XDG stuff + if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) { + xdg_config_home = "~/.config"; + } + wordexp(xdg_config_home, &exp_result, 0); + xdg_config_home = strdup(exp_result.we_wordv[0]); + wordfree(&exp_result); + + // Check postprocess.h in the current working directory + sprintf(script, "%s", filename); + if(access(script, F_OK) != -1) { + sprintf(script, "./%s", filename); + printf("Found postprocessor script at %s\n", script); + return true; + } + + // Check for a script in XDG_CONFIG_HOME + sprintf(script, "%s/megapixels/%s", xdg_config_home, filename); + if(access(script, F_OK) != -1) { + printf("Found postprocessor script at %s\n", script); + return true; + } + + // Check user overridden /etc/megapixels/postprocessor.sh + sprintf(script, "%s/megapixels/%s", SYSCONFDIR, filename); + if(access(script, F_OK) != -1) { + printf("Found postprocessor script at %s\n", script); + return true; + } + + // Check packaged /usr/share/megapixels/postprocessor.sh + sprintf(script, "%s/megapixels/%s", DATADIR, filename); + if(access(script, F_OK) != -1) { + printf("Found postprocessor script at %s\n", script); + return true; + } + + return false; +} + +static void setup(MPPipeline *pipeline, const void *data) +{ + TIFFSetTagExtender(register_custom_tiff_tags); + + if (!find_processor(processing_script)) { + g_printerr("Could not find any post-process script\n"); + exit(1); + } +} + +void mp_process_pipeline_start() +{ + pipeline = mp_pipeline_new(); + + mp_pipeline_invoke(pipeline, setup, NULL, 0); +} + +void mp_process_pipeline_stop() +{ + mp_pipeline_free(pipeline); +} + +static void +process_image_for_preview(const MPImage *image) +{ + uint32_t surface_width, surface_height, skip; + quick_preview_size( + &surface_width, + &surface_height, + &skip, + preview_width, + preview_height, + image->width, + image->height, + image->pixel_format, + camera->rotate); + + cairo_surface_t *surface = cairo_image_surface_create( + CAIRO_FORMAT_RGB24, + surface_width, + surface_height); + + uint8_t *pixels = cairo_image_surface_get_data(surface); + + quick_preview( + (uint32_t *)pixels, + surface_width, + surface_height, + image->data, + image->width, + image->height, + image->pixel_format, + camera->rotate, + camera->mirrored, + camera->colormatrix[0] == 0 ? NULL : camera->colormatrix, + camera->blacklevel, + skip); + + mp_main_set_preview(surface); +} + +static void +process_image_for_capture(const MPImage *image, int count) +{ + time_t rawtime; + time(&rawtime); + struct tm tim = *(localtime(&rawtime)); + + char datetime[20] = {0}; + strftime(datetime, 20, "%Y:%m:%d %H:%M:%S", &tim); + + char fname[255]; + sprintf(fname, "%s/%d.dng", burst_dir, count); + + TIFF *tif = TIFFOpen(fname, "w"); + if(!tif) { + printf("Could not open tiff\n"); + } + + // Define TIFF thumbnail + TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 1); + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, image->width >> 4); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, image->height >> 4); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_MAKE, mp_get_device_make()); + TIFFSetField(tif, TIFFTAG_MODEL, mp_get_device_model()); + uint16_t orientation; + if (camera->rotate == 0) { + orientation = camera->mirrored ? ORIENTATION_TOPRIGHT : ORIENTATION_TOPLEFT; + } else if (camera->rotate == 90) { + orientation = camera->mirrored ? ORIENTATION_RIGHTBOT : ORIENTATION_LEFTBOT; + } else if (camera->rotate == 180) { + orientation = camera->mirrored ? ORIENTATION_BOTLEFT : ORIENTATION_BOTRIGHT; + } else { + orientation = camera->mirrored ? ORIENTATION_LEFTTOP : ORIENTATION_RIGHTTOP; + } + TIFFSetField(tif, TIFFTAG_ORIENTATION, orientation); + TIFFSetField(tif, TIFFTAG_DATETIME, datetime); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_SOFTWARE, "Megapixels"); + long sub_offset = 0; + TIFFSetField(tif, TIFFTAG_SUBIFD, 1, &sub_offset); + TIFFSetField(tif, TIFFTAG_DNGVERSION, "\001\001\0\0"); + TIFFSetField(tif, TIFFTAG_DNGBACKWARDVERSION, "\001\0\0\0"); + char uniquecameramodel[255]; + sprintf(uniquecameramodel, "%s %s", mp_get_device_make(), mp_get_device_model()); + TIFFSetField(tif, TIFFTAG_UNIQUECAMERAMODEL, uniquecameramodel); + if(camera->colormatrix[0]) { + TIFFSetField(tif, TIFFTAG_COLORMATRIX1, 9, camera->colormatrix); + } else { + TIFFSetField(tif, TIFFTAG_COLORMATRIX1, 9, colormatrix_srgb); + } + if(camera->forwardmatrix[0]) { + TIFFSetField(tif, TIFFTAG_FORWARDMATRIX1, 9, camera->forwardmatrix); + } + static const float neutral[] = {1.0, 1.0, 1.0}; + TIFFSetField(tif, TIFFTAG_ASSHOTNEUTRAL, 3, neutral); + TIFFSetField(tif, TIFFTAG_CALIBRATIONILLUMINANT1, 21); + // Write black thumbnail, only windows uses this + { + unsigned char *buf = (unsigned char *)calloc(1, (int)image->width >> 4); + for (int row = 0; row < image->height>>4; row++) { + TIFFWriteScanline(tif, buf, row, 0); + } + free(buf); + } + TIFFWriteDirectory(tif); + + // Define main photo + TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 0); + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, image->width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, image->height); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + static const short cfapatterndim[] = {2, 2}; + TIFFSetField(tif, TIFFTAG_CFAREPEATPATTERNDIM, cfapatterndim); + TIFFSetField(tif, TIFFTAG_CFAPATTERN, "\002\001\001\000"); // BGGR + if(camera->whitelevel) { + TIFFSetField(tif, TIFFTAG_WHITELEVEL, 1, &camera->whitelevel); + } + if(camera->blacklevel) { + TIFFSetField(tif, TIFFTAG_BLACKLEVEL, 1, &camera->blacklevel); + } + TIFFCheckpointDirectory(tif); + printf("Writing frame to %s\n", fname); + + unsigned char *pLine = (unsigned char*)malloc(image->width); + for(int row = 0; row < image->height; row++){ + TIFFWriteScanline(tif, image->data + (row * image->width), row, 0); + } + free(pLine); + TIFFWriteDirectory(tif); + + // Add an EXIF block to the tiff + TIFFCreateEXIFDirectory(tif); + // 1 = manual, 2 = full auto, 3 = aperture priority, 4 = shutter priority + if (!exposure_is_manual) { + TIFFSetField(tif, EXIFTAG_EXPOSUREPROGRAM, 2); + } else { + TIFFSetField(tif, EXIFTAG_EXPOSUREPROGRAM, 1); + } + + TIFFSetField(tif, EXIFTAG_EXPOSURETIME, (mode.frame_interval.numerator / (float)mode.frame_interval.denominator) / ((float)image->height / (float)exposure)); + uint16_t isospeed[1]; + isospeed[0] = (uint16_t)remap(gain - 1, 0, gain_max, camera->iso_min, camera->iso_max); + TIFFSetField(tif, EXIFTAG_ISOSPEEDRATINGS, 1, isospeed); + TIFFSetField(tif, EXIFTAG_FLASH, 0); + + TIFFSetField(tif, EXIFTAG_DATETIMEORIGINAL, datetime); + TIFFSetField(tif, EXIFTAG_DATETIMEDIGITIZED, datetime); + if(camera->fnumber) { + TIFFSetField(tif, EXIFTAG_FNUMBER, camera->fnumber); + } + if(camera->focallength) { + TIFFSetField(tif, EXIFTAG_FOCALLENGTH, camera->focallength); + } + if(camera->focallength && camera->cropfactor) { + TIFFSetField(tif, EXIFTAG_FOCALLENGTHIN35MMFILM, (short)(camera->focallength * camera->cropfactor)); + } + uint64_t exif_offset = 0; + TIFFWriteCustomDirectory(tif, &exif_offset); + TIFFFreeDirectory(tif); + + // Update exif pointer + TIFFSetDirectory(tif, 0); + TIFFSetField(tif, TIFFTAG_EXIFIFD, exif_offset); + TIFFRewriteDirectory(tif); + + TIFFClose(tif); +} + +static void +process_capture_burst() +{ + time_t rawtime; + time(&rawtime); + struct tm tim = *(localtime(&rawtime)); + + char timestamp[30]; + strftime(timestamp, 30, "%Y%m%d%H%M%S", &tim); + + sprintf(capture_fname, "%s/Pictures/IMG%s", getenv("HOME"), timestamp); + + // Start post-processing the captured burst + g_print("Post process %s to %s.ext\n", burst_dir, capture_fname); + char command[1024]; + sprintf(command, "%s %s %s &", processing_script, burst_dir, capture_fname); + system(command); +} + +static void +process_image(MPPipeline *pipeline, const MPImage *image) +{ + assert(image->width == mode.width && image->height == mode.height); + + process_image_for_preview(image); + + if (captures_remaining > 0) { + int count = burst_length - captures_remaining; + --captures_remaining; + + process_image_for_capture(image, count); + + if (captures_remaining == 0) { + process_capture_burst(); + + mp_main_capture_completed(capture_fname); + } + } + + free(image->data); + + ++frames_processed; + if (captures_remaining == 0) { + is_capturing = false; + } +} + +void mp_process_pipeline_process_image(MPImage image) +{ + // If we haven't processed the previous frame yet, drop this one + if (frames_received != frames_processed && !is_capturing) { + printf("Dropped frame at capture %d %d\n", frames_received, frames_processed); + return; + } + + ++frames_received; + + mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_image, &image, sizeof(MPImage)); +} + +static void capture() +{ + char template[] = "/tmp/megapixels.XXXXXX"; + char *tempdir; + tempdir = mkdtemp(template); + + if (tempdir == NULL) { + g_printerr("Could not make capture directory %s\n", template); + exit (EXIT_FAILURE); + } + + strcpy(burst_dir, tempdir); + + captures_remaining = burst_length; +} + +void mp_process_pipeline_capture() +{ + is_capturing = true; + + mp_pipeline_invoke(pipeline, capture, NULL, 0); +} + +static void +update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state) +{ + camera = state->camera; + mode = state->mode; + + burst_length = state->burst_length; + + preview_width = state->preview_width; + preview_height = state->preview_height; + + // gain_is_manual = state->gain_is_manual; + gain = state->gain; + gain_max = state->gain_max; + + exposure_is_manual = state->exposure_is_manual; + exposure = state->exposure; + + struct mp_main_state main_state = { + .camera = camera, + .mode = mode, + .gain_is_manual = state->gain_is_manual, + .gain = gain, + .gain_max = gain_max, + .exposure_is_manual = exposure_is_manual, + .exposure = exposure, + .has_auto_focus_continuous = state->has_auto_focus_continuous, + .has_auto_focus_start = state->has_auto_focus_start, + }; + mp_main_update_state(&main_state); +} + +void mp_process_pipeline_update_state(const struct mp_process_pipeline_state *new_state) +{ + mp_pipeline_invoke(pipeline, (MPPipelineCallback)update_state, new_state, sizeof(struct mp_process_pipeline_state)); +} diff --git a/process_pipeline.h b/process_pipeline.h new file mode 100644 index 0000000..8886f17 --- /dev/null +++ b/process_pipeline.h @@ -0,0 +1,30 @@ +#pragma once + +#include "camera_config.h" + +struct mp_process_pipeline_state { + const struct mp_camera_config *camera; + MPCameraMode mode; + + int burst_length; + + int preview_width; + int preview_height; + + bool gain_is_manual; + int gain; + int gain_max; + + bool exposure_is_manual; + int exposure; + + bool has_auto_focus_continuous; + bool has_auto_focus_start; +}; + +void mp_process_pipeline_start(); +void mp_process_pipeline_stop(); + +void mp_process_pipeline_process_image(MPImage image); +void mp_process_pipeline_capture(); +void mp_process_pipeline_update_state(const struct mp_process_pipeline_state *state); diff --git a/tools/test_camera.c b/tools/test_camera.c index 22d5414..3f18439 100644 --- a/tools/test_camera.c +++ b/tools/test_camera.c @@ -16,7 +16,7 @@ double get_time() void on_capture(MPImage image, void *user_data) { - size_t num_bytes = mp_pixel_format_bytes_per_pixel(image.pixel_format) * image.width * image.height; + size_t num_bytes = mp_pixel_format_width_to_bytes(image.pixel_format, image.width) * image.height; uint8_t *data = malloc(num_bytes); memcpy(data, image.data, num_bytes);