/* * OMAP3 ISP snapshot test application * * Copyright (C) 2010-2011 Ideas on board SPRL * * Contact: Laurent Pinchart * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define _BSD_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if USE_LIBJPEG #include #include #endif #include "isp/list.h" #include "isp/omap3isp.h" #include "isp/tools.h" #include "isp/v4l2.h" #include "isp/v4l2-pool.h" #include "events.h" #include "iq.h" #include "jpeg.h" #define MEDIA_DEVICE "/dev/media0" #define VIEWFINDER_WIDTH 1024 #define VIEWFINDER_HEIGHT 768 #define SNAPSHOT_WIDTH 2048 #define SNAPSHOT_HEIGHT 1536 struct viewfinder { unsigned int count; bool enabled; struct v4l2_buffers_pool *pool; }; static struct viewfinder vf = { 0, false, NULL }; enum snapshot_trigger_type { SNAPSHOT_TRIGGER_AUTO = 0, SNAPSHOT_TRIGGER_MANUAL, }; struct snapshot { struct v4l2_mbus_framefmt format; struct v4l2_rect crop; unsigned int cycle; unsigned int ncycles; unsigned int frame; unsigned int nframes; unsigned int interval; unsigned int skip; struct timespec time; enum snapshot_trigger_type trigger; bool save; bool jpeg_enable; struct jpeg *jpeg; }; static struct snapshot snap = { .crop = { -1, -1, -1, -1 }, .ncycles = 1, .nframes = 3, .interval = 20, .skip = 2, }; static struct iq_tuning *iq = NULL; static struct events events; /* ----------------------------------------------------------------------------- * Image quality */ static void __iq_aewb_process(struct omap3_isp_device *isp __attribute__((__unused__)), const struct omap3_isp_aewb_stats *stats) { iq_aewb_process(iq, stats); } /* ----------------------------------------------------------------------------- * Events */ static void __events_watch_fd(int fd, enum omap3_isp_event_type type, void(*callback)(void *), void *priv) { events_watch_fd(&events, fd, type, callback, priv); } static void __events_unwatch_fd(int fd) { events_unwatch_fd(&events, fd); } static void sigint_handler(int signal __attribute__((__unused__))) { /* Stop the main loop when the user presses CTRL-C. */ events_stop(&events); } /* ----------------------------------------------------------------------------- * Snapshots */ static int snapshot_capture(struct omap3_isp_device *isp) { printf("- snapshot %u\n", snap.cycle); snap.cycle++; snap.frame = 0; clock_gettime(CLOCK_MONOTONIC, &snap.time); return omap3_isp_snapshot_capture(isp); } static void snapshot_process(struct omap3_isp_device *isp, struct v4l2_video_buffer *buffer) { static struct timespec ts; char name[33]; FILE *file; int ret; if (buffer->error) { printf("warning: error in dequeued buffer, skipping\n"); goto requeue; } ts.tv_sec = buffer->timestamp.tv_sec - snap.time.tv_sec; ts.tv_nsec = (buffer->timestamp.tv_usec * 1000) - snap.time.tv_nsec; if (ts.tv_nsec < 0) { ts.tv_sec--; ts.tv_nsec += 1000000000; } snap.time.tv_sec = buffer->timestamp.tv_sec; snap.time.tv_nsec = buffer->timestamp.tv_usec * 1000; printf("frame captured in %lu.%06lus.\n", ts.tv_sec, ts.tv_nsec / 1000); if (snap.save && snap.frame >= snap.skip) { sprintf(name, "snapshot-%06u-%02u.%s", snap.cycle, snap.frame, #if USE_LIBJPEG snap.jpeg ? "jpg" : #endif "bin"); file = fopen(name, "wb"); if (file == NULL) goto requeue; #if USE_LIBJPEG if (snap.jpeg) jpeg_save(snap.jpeg, file, buffer); else #endif fwrite(buffer->mem, buffer->bytesused, 1, file); fclose(file); } requeue: /* Requeue the buffer */ ret = omap3_isp_snapshot_put_buffer(isp, buffer); if (ret < 0) { printf("error: unable to requeue buffer: %s (%d)\n", strerror(-ret), ret); return; } if (buffer->error) return; snap.frame++; if (snap.frame < snap.nframes) return; if (snap.trigger == SNAPSHOT_TRIGGER_AUTO) events_stop(&events); omap3_isp_snapshot_done(isp); if (vf.enabled) { ret = omap3_isp_viewfinder_start(isp, -1); if (ret < 0) { printf("error: unable to restart viewfinder: %s (%d)\n", strerror(-ret), ret); return; } } } static int snapshot_init(struct omap3_isp_device *isp) { struct v4l2_rect *crop; int ret; if (snap.crop.width > 0 && snap.crop.height > 0) crop = &snap.crop; else crop = NULL; ret = omap3_isp_snapshot_setup(isp, crop, &snap.format); if (ret < 0) { printf("error: unable to setup pipeline\n"); return ret; } #if USE_LIBJPEG if (snap.jpeg_enable) { snap.jpeg = jpeg_init(&snap.format); if (snap.jpeg == NULL) { printf("Unable to initialize JPEG compressor.\n"); return -EIO; } } #endif printf("snapshot configured for %04x %ux%u\n", snap.format.code, snap.format.width, snap.format.height); return 0; } static void snapshot_cleanup(void) { #if USE_LIBJPEG if (snap.jpeg != NULL) jpeg_cleanup(snap.jpeg); #endif } /* ----------------------------------------------------------------------------- * Viewfinder */ static void viewfinder_process(struct omap3_isp_device *isp, struct v4l2_video_buffer *buffer) { int ret; if (buffer->error) { printf("warning: error in dequeued buffer, skipping\n"); return; } vf.count++; printf("viewfinder frame %u captured.\n", vf.count); /* Requeue the buffer */ ret = omap3_isp_viewfinder_put_buffer(isp, buffer); if (ret < 0) { printf("error: unable to requeue buffer: %s (%d)\n", strerror(-ret), ret); return; } if ((vf.count % snap.interval) == 0) { ret = omap3_isp_viewfinder_stop(isp); if (ret < 0) { printf("error: unable to stop viewfinder: %s (%d)\n", strerror(-ret), ret); return; } snapshot_capture(isp); } } static int viewfinder_init(struct omap3_isp_device *isp) { struct v4l2_mbus_framefmt view_format; int ret; memset(&view_format, 0, sizeof view_format); view_format.code = V4L2_MBUS_FMT_YUYV8_1X16; view_format.width = VIEWFINDER_WIDTH; view_format.height = VIEWFINDER_HEIGHT; ret = omap3_isp_viewfinder_setup(isp, NULL, &view_format); if (ret < 0) { printf("error: unable to setup pipeline\n"); return ret; } printf("viewfinder configured for %04x %ux%u\n", view_format.code, view_format.width, view_format.height); /* Allocate a buffers pool and use it for the viewfinder. */ vf.pool = v4l2_buffers_pool_new(4); if (vf.pool == NULL) { printf("error: unable to create buffers pool\n"); return -ENOMEM; } ret = v4l2_buffers_pool_alloc(vf.pool, view_format.width * view_format.height * 2, 4096); if (ret < 0) { printf("error: unable to allocate buffers memory\n"); return ret; } ret = omap3_isp_viewfinder_set_pool(isp, vf.pool); if (ret < 0) { printf("error: unable to set buffers pool\n"); return ret; } return 0; } static void viewfinder_cleanup(struct omap3_isp_device *isp __attribute__((__unused__))) { if (vf.pool) v4l2_buffers_pool_delete(vf.pool); } /* -------------------------------------------------------------------------- */ static const struct { const char *name; unsigned int fourcc; } pixel_formats[] = { { "Y8", V4L2_MBUS_FMT_Y8_1X8 }, { "Y10", V4L2_MBUS_FMT_Y10_1X10 }, { "Y12", V4L2_MBUS_FMT_Y12_1X12 }, { "YUYV", V4L2_MBUS_FMT_YUYV8_1X16 }, { "UYVY", V4L2_MBUS_FMT_UYVY8_1X16 }, { "SBGGR8", V4L2_MBUS_FMT_SBGGR8_1X8 }, { "SGBRG8", V4L2_MBUS_FMT_SGBRG8_1X8 }, { "SGRBG8", V4L2_MBUS_FMT_SGRBG8_1X8 }, { "SRGGB8", V4L2_MBUS_FMT_SRGGB8_1X8 }, { "SBGGR10", V4L2_MBUS_FMT_SBGGR10_1X10 }, { "SGBRG10", V4L2_MBUS_FMT_SGBRG10_1X10 }, { "SGRBG10", V4L2_MBUS_FMT_SGRBG10_1X10 }, { "SRGGB10", V4L2_MBUS_FMT_SRGGB10_1X10 }, { "SBGGR12", V4L2_MBUS_FMT_SBGGR12_1X12 }, { "SGBRG12", V4L2_MBUS_FMT_SGBRG12_1X12 }, { "SGRBG12", V4L2_MBUS_FMT_SGRBG12_1X12 }, { "SRGGB12", V4L2_MBUS_FMT_SRGGB12_1X12 }, }; static unsigned int parse_format(const char *name) { unsigned int i; for (i = 0; i < ARRAY_SIZE(pixel_formats); ++i) { if (strcasecmp(pixel_formats[i].name, name) == 0) return pixel_formats[i].fourcc; } return 0; } static int parse_crop(const char *p, struct v4l2_rect *crop) { char *end; if (*p++ != '(') return -EINVAL; crop->left = strtoul(p, &end, 10); if (*end != ',') return -EINVAL; p = end + 1; crop->top = strtoul(p, &end, 10); if (*end++ != ')') return -EINVAL; if (*end != '/') return -EINVAL; p = end + 1; crop->width = strtoul(p, &end, 10); if (*end != 'x') return -EINVAL; p = end + 1; crop->height = strtoul(p, &end, 10); if (*end != '\0') return -EINVAL; return 0; } static int parse_size(const char *p, struct v4l2_mbus_framefmt *format) { char *end; format->width = strtoul(p, &end, 10); if (*end != 'x') return -EINVAL; p = end + 1; format->height = strtoul(p, &end, 10); if (*end != '\0') return -EINVAL; return 0; } static void usage(const char *argv0) { printf("Usage: %s [options]\n", argv0); printf("Supported options:\n"); printf("-c, --crop (x,y)/wxh Set the snapshot capture crop\n"); printf("-f, --format fmt Snapshot format\n"); printf("-h, --help Show this help screen\n"); printf("-i, --interval n Capture a snapshot every n frames (default 20)\n"); #if USE_LIBJPEG printf("-j, --jpeg Save snapshots in JPEG format (implies YUV mode)\n"); #endif printf("-k, --skip n Skip n frames per snapshot when saving to disk (default 2)\n"); printf("-N, --snap-frames n Capture n frames per snapshot (default 3)\n"); printf("-n, --snap-number n Capture n snapshots (default 1)\n"); printf("-S, --save Save snapshots to disk\n"); printf("-s, --size wxh Set the snapshot capture size\n"); printf("-v, --view Enable viewfinder\n"); printf(" --aewb param=value Set AEWB algorithm parameter 'param' to 'value'\n"); printf(" --disable-aewb Disable AEWB\n"); printf(" --saturation value Set the saturation value [0.0-2.0]\n"); printf("\nSupported AEWB parameters are:\n"); printf("- ae-delay Number of frames to skip at stream start before enabling AE\n"); printf("- ae-interval Number of frames between AE algorithm runs (>= 1)\n"); printf("- exposure-def Exposure time default value\n"); printf("- exposure-min Exposure time minimum value\n"); printf("- exposure-max Exposure time maximum value\n"); printf("- gain-def Sensor gain default value\n"); printf("- gain-min Sensor gain minimum value\n"); printf("- gain-max Sensor gain maximum value\n"); printf("- mean-level Mean luminance target level (float [0-1])\n"); printf("- window-left Statistics window left (float [0-1])\n"); printf("- window-top Statistics window top (float [0-1])\n"); printf("- window-width Statistics window width (float [0-1])\n"); printf("- window-height Statistics window height (float [0-1])\n"); } #define OPT_AEWB_PARAM 256 #define OPT_SATURATION 257 #define OPT_AEWB_DISABLE 258 static struct option opts[] = { { "aewb", 1, 0, OPT_AEWB_PARAM }, { "crop", 1, 0, 'c' }, { "disable-aewb", 0, 0, OPT_AEWB_DISABLE }, { "format", 1, 0, 'f' }, { "help", 0, 0, 'h' }, { "interval", 1, 0, 'i' }, #if USE_LIBJPEG { "jpeg", 0, 0, 'j' }, #endif { "saturation", 1, 0, OPT_SATURATION }, { "save", 0, 0, 'S' }, { "size", 1, 0, 's' }, { "snap-frames", 1, 0, 'N' }, { "snap-number", 1, 0, 'n' }, { "skip", 1, 0, 'k' }, { "view", 1, 0, 'v' }, { 0, 0, 0, 0 } }; int main(int argc __attribute__((__unused__)), char *argv[] __attribute__((__unused__))) { struct omap3_isp_device *isp = NULL; struct omap3_isp_operations ops; struct timespec start, end; struct iq_params iq_params; int exit_code = EXIT_FAILURE; bool enable_aewb = true; float fps; int ret; int c; snap.format.code = V4L2_MBUS_FMT_SGRBG10_1X10; snap.format.width = SNAPSHOT_WIDTH; snap.format.height = SNAPSHOT_HEIGHT; iq_params_init(&iq_params); while ((c = getopt_long(argc, argv, "c:f:hi:" #if USE_LIBJPEG "j" #endif "k:N:n:Ss:v", opts, NULL)) != -1) { switch (c) { case 'c': if (parse_crop(optarg, &snap.crop)) { printf("invalid crop rectangle %s.\n", optarg); return 1; } break; case 'f': snap.format.code = parse_format(optarg); if (snap.format.code == 0) { printf("invalid format %s.\n", optarg); return 1; } break; case 'h': usage(argv[0]); return 0; case 'i': snap.interval = strtoul(optarg, NULL, 10); if (snap.interval == 0) { printf("snapshot interval value must be >0.\n"); return 1; } break; #if USE_LIBJPEG case 'j': snap.jpeg_enable = true; snap.format.code = V4L2_MBUS_FMT_YUYV8_1X16; break; #endif case 'k': snap.skip = strtoul(optarg, NULL, 10); break; case 'N': snap.nframes = strtoul(optarg, NULL, 10); if (snap.nframes == 0) { printf("number of frames must be >0.\n"); return 1; } break; case 'n': snap.ncycles = strtoul(optarg, NULL, 10); if (snap.ncycles == 0) { printf("number of snapshots must be >0.\n"); return 1; } break; case 'S': snap.save = true; break; case 's': if (parse_size(optarg, &snap.format)) { printf("invalid size %s.\n", optarg); return 1; } break; case 'v': vf.enabled = true; snap.trigger = SNAPSHOT_TRIGGER_MANUAL; break; case OPT_AEWB_DISABLE: enable_aewb = false; break; case OPT_AEWB_PARAM: ret = iq_params_parse(&iq_params, optarg); if (ret < 0) { printf("%s AEWB argument %s.\n", ret == -ERANGE ? "out-of-range" : "invalid", optarg); return 1; } break; case OPT_SATURATION: iq_params.saturation = strtof(optarg, NULL); break; default: printf("Invalid option -%c\n", c); printf("Run %s -h for help.\n", argv[0]); return 1; } } events_init(&events); /* Register a signal handler for SIGINT, received when the user presses * CTRL-C. This will allow the main loop to be interrupted, and resources * to be freed cleanly. */ signal(SIGINT, sigint_handler); /* Open the OMAP3 ISP device and setup the capture pipeline. */ memset(&ops, 0, sizeof ops); ops.viewfinder_ready = viewfinder_process; ops.snapshot_ready = snapshot_process; ops.watch_fd = __events_watch_fd; ops.unwatch_fd = __events_unwatch_fd; ops.aewb_ready = __iq_aewb_process; isp = omap3_isp_open(MEDIA_DEVICE, &ops); if (isp == NULL) { printf("error: unable to open media device %s\n", MEDIA_DEVICE); goto cleanup; } if (vf.enabled) { ret = viewfinder_init(isp); if (ret < 0) goto cleanup; } ret = snapshot_init(isp); if (ret < 0) goto cleanup; if (enable_aewb) { iq = iq_init(isp, &iq_params); if (iq == NULL) { printf("error: unable to initialize image quality tuning\n"); goto cleanup; } } /* Start the viewfinder if needed. */ if (vf.enabled) { ret = omap3_isp_viewfinder_start(isp, -1); if (ret < 0) goto cleanup; } /* Main capture loop. */ clock_gettime(CLOCK_MONOTONIC, &start); for (snap.cycle = 0; snap.cycle < snap.ncycles; ) { if (!vf.enabled) { ret = snapshot_capture(isp); if (ret < 0) goto cleanup; } if (events_loop(&events)) goto cleanup; } clock_gettime(CLOCK_MONOTONIC, &end); /* Stop the ISP. */ if (vf.enabled) omap3_isp_viewfinder_stop(isp); /* Print some statistics. */ end.tv_sec -= start.tv_sec; end.tv_nsec -= start.tv_nsec; if (end.tv_nsec < 0) { end.tv_sec--; end.tv_nsec += 1000000000; } fps = (vf.count + snap.cycle * snap.frame) / (end.tv_sec + end.tv_nsec / 1000000000.0); printf("%u images processed in %lu.%06lu seconds (%f fps)\n", vf.count + snap.cycle * snap.frame, end.tv_sec, end.tv_nsec / 1000, fps); exit_code = EXIT_SUCCESS; cleanup: /* Cleanup the ISP resources. */ if (isp) omap3_isp_close(isp); if (vf.enabled) viewfinder_cleanup(isp); if (iq) iq_cleanup(iq); snapshot_cleanup(); return exit_code; }