From 94d437df30826f3e4dfeffa42ee4420a2654aaa3 Mon Sep 17 00:00:00 2001 From: Martijn Braam Date: Thu, 23 Jan 2020 23:04:36 +0100 Subject: [PATCH] Added config file parser and meson script Includes style fixes --- .gitignore | 9 +- CMakeLists.txt | 2 +- ini.c | 195 ++++++++++++++++++ ini.h | 93 +++++++++ main.c | 531 ++++++++++++++++++++++++++----------------------- meson.build | 3 + uvc.ini | 6 + 7 files changed, 590 insertions(+), 249 deletions(-) create mode 100644 ini.c create mode 100644 ini.h create mode 100644 meson.build create mode 100644 uvc.ini diff --git a/.gitignore b/.gitignore index 6577148..93499a9 100644 --- a/.gitignore +++ b/.gitignore @@ -88,4 +88,11 @@ _deps /.idea /cmake-build-debug /cmake-build-release -*~ \ No newline at end of file +*~ + +/.ninja_deps +/.ninja_log +/build.ninja +/meson-info +/meson-private +/builddir \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a240f05..790bf0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,5 +15,5 @@ LINK_DIRECTORIES(${GTK3_LIBRARY_DIRS}) # Add other flags to the compiler ADD_DEFINITIONS(${GTK3_CFLAGS_OTHER}) -add_executable(GTKCamera main.c) +add_executable(GTKCamera main.c ini.c ini.h) target_link_libraries(GTKCamera ${GTK3_LIBRARIES}) \ No newline at end of file diff --git a/ini.c b/ini.c new file mode 100644 index 0000000..6bc1eae --- /dev/null +++ b/ini.c @@ -0,0 +1,195 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#include +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to null at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + strncpy(dest, src, size-1); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; +#else + char* line; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)malloc(INI_MAX_LINE); + if (!line) { + return -2; + } +#endif + + /* Scan through stream line by line */ + while (reader(line, INI_MAX_LINE, stream) != NULL) { + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (*start == ';' || *start == '#') { + /* Per Python configparser, allow both ; and # comments at the + start of a line */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!handler(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = lskip(end + 1); +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!handler(user, section, name, value) && !error) + error = lineno; + memset(value, 0, strlen(value)); + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} diff --git a/ini.h b/ini.h new file mode 100644 index 0000000..2804255 --- /dev/null +++ b/ini.h @@ -0,0 +1,93 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Typedef for prototype of handler function. */ +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See http://code.google.com/p/inih/issues/detail?id=21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Maximum line length for any line in INI file. */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 2000 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __INI_H__ */ diff --git a/main.c b/main.c index 51d3e23..a0df096 100644 --- a/main.c +++ b/main.c @@ -5,8 +5,7 @@ #include #include #include - -#define CLEAR(x) memset(&(x), 0, sizeof(x)) +#include "ini.h" enum io_method { IO_METHOD_READ, @@ -19,88 +18,97 @@ struct buffer { size_t length; }; - struct buffer *buffers; static unsigned int n_buffers; +static char *rear_dev_name; +static char *front_dev_name; static char *dev_name; static enum io_method io = IO_METHOD_MMAP; -static int force_format_width = 640; -static int force_format_height = 480; + +static int preview_width = -1; +static int preview_height = -1; +static int preview_fmt = V4L2_PIX_FMT_RGB24; + GObject *preview_image; - -static int xioctl(int fd, int request, void *arg) { +static int +xioctl(int fd, int request, void *arg) +{ int r; - do r = ioctl(fd, request, arg); - while (-1 == r && EINTR == errno); + do { + r = ioctl(fd, request, arg); + } while (r == -1 && errno == EINTR); return r; } -static void errno_exit(const char *s) { +static void +errno_exit(const char *s) +{ fprintf(stderr, "%s error %d, %s\\n", s, errno, strerror(errno)); exit(EXIT_FAILURE); } -static void start_capturing(int fd) { - unsigned int i; +static void +start_capturing(int fd) +{ enum v4l2_buf_type type; - switch (io) { - case IO_METHOD_READ: - /* Nothing to do. */ - break; + case IO_METHOD_READ: + /* Nothing to do. */ + break; + case IO_METHOD_MMAP: + for (int i = 0; i < n_buffers; ++i) { + struct v4l2_buffer buf = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .memory = V4L2_MEMORY_MMAP, + .index = i, + }; - case IO_METHOD_MMAP: - for (i = 0; i < n_buffers; ++i) { - struct v4l2_buffer buf; - - CLEAR(buf); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_MMAP; - buf.index = i; - - if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) - errno_exit("VIDIOC_QBUF"); + if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { + errno_exit("VIDIOC_QBUF"); } - type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) - errno_exit("VIDIOC_STREAMON"); - break; + } - case IO_METHOD_USERPTR: - for (i = 0; i < n_buffers; ++i) { - struct v4l2_buffer buf; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (xioctl(fd, VIDIOC_STREAMON, &type) == -1) { + errno_exit("VIDIOC_STREAMON"); + } + break; + case IO_METHOD_USERPTR: + for (int i = 0; i < n_buffers; ++i) { + struct v4l2_buffer buf = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .memory = V4L2_MEMORY_USERPTR, + .index = i, + }; + buf.m.userptr = (unsigned long)buffers[i].start; + buf.length = buffers[i].length; - CLEAR(buf); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_USERPTR; - buf.index = i; - buf.m.userptr = (unsigned long) buffers[i].start; - buf.length = buffers[i].length; - - if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) - errno_exit("VIDIOC_QBUF"); + if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { + errno_exit("VIDIOC_QBUF"); } - type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) - errno_exit("VIDIOC_STREAMON"); - break; + } + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (xioctl(fd, VIDIOC_STREAMON, &type) == -1) { + errno_exit("VIDIOC_STREAMON"); + } + break; } } -static void init_mmap(int fd) { - struct v4l2_requestbuffers req; - - CLEAR(req); - +static void +init_mmap(int fd) +{ + struct v4l2_requestbuffers req = { 0 }; req.count = 4; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; - if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { - if (EINVAL == errno) { - fprintf(stderr, "%s does not support " - "memory mappingn", dev_name); + if (xioctl(fd, VIDIOC_REQBUFS, &req) == -1) { + if (errno == EINVAL) { + fprintf(stderr, "%s does not support memory mapping", + dev_name); exit(EXIT_FAILURE); } else { errno_exit("VIDIOC_REQBUFS"); @@ -108,12 +116,12 @@ static void init_mmap(int fd) { } if (req.count < 2) { - fprintf(stderr, "Insufficient buffer memory on %s\\n", + fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name); exit(EXIT_FAILURE); } - buffers = calloc(req.count, sizeof(*buffers)); + buffers = calloc(req.count, sizeof(buffers[0])); if (!buffers) { fprintf(stderr, "Out of memory\\n"); @@ -121,40 +129,36 @@ static void init_mmap(int fd) { } for (n_buffers = 0; n_buffers < req.count; ++n_buffers) { - struct v4l2_buffer buf; + struct v4l2_buffer buf = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .memory = V4L2_MEMORY_MMAP, + .index = n_buffers, + }; - CLEAR(buf); - - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_MMAP; - buf.index = n_buffers; - - if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) + if (xioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) { errno_exit("VIDIOC_QUERYBUF"); + } 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); + 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) + if (MAP_FAILED == buffers[n_buffers].start) { errno_exit("mmap"); + } } } -static void init_device(int fd) { +static void +init_device(int fd) +{ struct v4l2_capability cap; - struct v4l2_cropcap cropcap; - struct v4l2_crop crop; - struct v4l2_format fmt; - unsigned int min; - - if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) { - if (EINVAL == errno) { - fprintf(stderr, "%s is no V4L2 device\\n", + if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) { + if (errno == EINVAL) { + fprintf(stderr, "%s is no V4L2 device\n", dev_name); exit(EXIT_FAILURE); } else { @@ -163,50 +167,47 @@ static void init_device(int fd) { } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { - fprintf(stderr, "%s is no video capture device\\n", + fprintf(stderr, "%s is no video capture device\n", dev_name); exit(EXIT_FAILURE); } switch (io) { - case IO_METHOD_READ: - if (!(cap.capabilities & V4L2_CAP_READWRITE)) { - fprintf(stderr, "%s does not support read i/o\\n", - dev_name); - exit(EXIT_FAILURE); - } - break; - - case IO_METHOD_MMAP: - case IO_METHOD_USERPTR: - if (!(cap.capabilities & V4L2_CAP_STREAMING)) { - fprintf(stderr, "%s does not support streaming i/o\\n", - dev_name); - exit(EXIT_FAILURE); - } - break; + case IO_METHOD_READ: + if (!(cap.capabilities & V4L2_CAP_READWRITE)) { + fprintf(stderr, "%s does not support read i/o\n", + dev_name); + exit(EXIT_FAILURE); + } + break; + case IO_METHOD_MMAP: + case IO_METHOD_USERPTR: + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + fprintf(stderr, "%s does not support streaming i/o\n", + dev_name); + exit(EXIT_FAILURE); + } + break; } - /* Select video input, video standard and tune here. */ + struct v4l2_cropcap cropcap = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + }; - - CLEAR(cropcap); - - cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - - if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) { + struct v4l2_crop crop = { 0 }; + if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) { crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; crop.c = cropcap.defrect; /* reset to default */ - if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) { + if (xioctl(fd, VIDIOC_S_CROP, &crop) == -1) { switch (errno) { - case EINVAL: - /* Cropping not supported. */ - break; - default: - /* Errors ignored. */ - break; + case EINVAL: + /* Cropping not supported. */ + break; + default: + /* Errors ignored. */ + break; } } } else { @@ -214,148 +215,144 @@ static void init_device(int fd) { } - CLEAR(fmt); - - fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (force_format_width > 0) { - fmt.fmt.pix.width = force_format_width; - fmt.fmt.pix.height = force_format_height; - fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; + struct v4l2_format fmt = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + }; + if (preview_width > 0) { + fmt.fmt.pix.width = preview_width; + fmt.fmt.pix.height = preview_height; + fmt.fmt.pix.pixelformat = preview_fmt; fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; - printf("Requesting a cooler format!\n"); - if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt)) + if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) { errno_exit("VIDIOC_S_FMT"); + } /* Note VIDIOC_S_FMT may change width and height. */ } else { /* Preserve original settings as set by v4l2-ctl for example */ - if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt)) + if (xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) { errno_exit("VIDIOC_G_FMT"); + } } /* Buggy driver paranoia. */ - min = fmt.fmt.pix.width * 2; - if (fmt.fmt.pix.bytesperline < min) + unsigned int min = fmt.fmt.pix.width * 2; + if (fmt.fmt.pix.bytesperline < min) { fmt.fmt.pix.bytesperline = min; + } min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; - if (fmt.fmt.pix.sizeimage < min) + if (fmt.fmt.pix.sizeimage < min) { fmt.fmt.pix.sizeimage = min; + } switch (io) { - case IO_METHOD_READ: - //init_read(fmt.fmt.pix.sizeimage); - break; + case IO_METHOD_READ: + //init_read(fmt.fmt.pix.sizeimage); + break; - case IO_METHOD_MMAP: - init_mmap(fd); - break; + case IO_METHOD_MMAP: + init_mmap(fd); + break; - case IO_METHOD_USERPTR: - //init_userp(fmt.fmt.pix.sizeimage); - break; + case IO_METHOD_USERPTR: + //init_userp(fmt.fmt.pix.sizeimage); + break; } } -static void process_image(const void *p, int size) { - GdkPixbuf *pixbuf; - pixbuf = gdk_pixbuf_new_from_data(p, GDK_COLORSPACE_RGB, FALSE, 8, 640, 480, 2 * 640, NULL, NULL); +static void +process_image(const void *p, int size) +{ + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(p, GDK_COLORSPACE_RGB, + FALSE, 8, 640, 480, 2 * 640, NULL, NULL); gtk_image_set_from_pixbuf(preview_image, pixbuf); } -static int read_frame(int fd) { - struct v4l2_buffer buf; - unsigned int i; +static int +read_frame(int fd) +{ + struct v4l2_buffer buf = { 0 }; switch (io) { - case IO_METHOD_READ: - if (-1 == read(fd, buffers[0].start, buffers[0].length)) { - switch (errno) { - case EAGAIN: - return 0; - - case EIO: - /* Could ignore EIO, see spec. */ - - /* fall through */ - - default: - errno_exit("read"); - } + case IO_METHOD_READ: + if (read(fd, buffers[0].start, buffers[0].length) == -1) { + switch (errno) { + case EAGAIN: + return 0; + case EIO: + /* Could ignore EIO, see spec. */ + /* fallthrough */ + default: + errno_exit("read"); + break; } - - process_image(buffers[0].start, buffers[0].length); - break; - - case IO_METHOD_MMAP: - CLEAR(buf); - - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_MMAP; - - if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { - switch (errno) { - case EAGAIN: - return 0; - - case EIO: - /* Could ignore EIO, see spec. */ - - /* fall through */ - - default: - errno_exit("VIDIOC_DQBUF"); - } + } + process_image(buffers[0].start, buffers[0].length); + break; + case IO_METHOD_MMAP: + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + 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; } + } - //assert(buf.index < n_buffers); + //assert(buf.index < n_buffers); - process_image(buffers[buf.index].start, buf.bytesused); + process_image(buffers[buf.index].start, buf.bytesused); - if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) - errno_exit("VIDIOC_QBUF"); - break; - - case IO_METHOD_USERPTR: - CLEAR(buf); - - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - buf.memory = V4L2_MEMORY_USERPTR; - - if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { - switch (errno) { - case EAGAIN: - return 0; - - case EIO: - /* Could ignore EIO, see spec. */ - - /* fall through */ - - default: - errno_exit("VIDIOC_DQBUF"); - } + if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { + errno_exit("VIDIOC_QBUF"); + } + break; + case IO_METHOD_USERPTR: + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_USERPTR; + 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; } + } + unsigned int i; + for (i = 0; i < n_buffers; ++i) { + if (buf.m.userptr == (unsigned long)buffers[i].start + && buf.length == buffers[i].length) { + break; + } + } - for (i = 0; i < n_buffers; ++i) - if (buf.m.userptr == (unsigned long) buffers[i].start - && buf.length == buffers[i].length) - break; + //assert(i < n_buffers); - //assert(i < n_buffers); - - process_image((void *) buf.m.userptr, buf.bytesused); - - if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) - errno_exit("VIDIOC_QBUF"); - break; + process_image((void *)buf.m.userptr, buf.bytesused); + if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) { + errno_exit("VIDIOC_QBUF"); + } + break; } return 1; } -static void get_frame(int fd) { - for (;;) { +static void +get_frame(int fd) +{ + while (1) { fd_set fds; struct timeval tv; int r; @@ -369,67 +366,107 @@ static void get_frame(int fd) { r = select(fd + 1, &fds, NULL, NULL, &tv); - if (-1 == r) { - if (EINTR == errno) + if (r == -1) { + if (EINTR == errno) { continue; + } errno_exit("select"); - } - - if (0 == r) { + } else if (r == 0) { fprintf(stderr, "select timeout\\n"); exit(EXIT_FAILURE); } - if (read_frame(fd)) + if (read_frame(fd)) { break; + } /* EAGAIN - continue select loop. */ } } -int main(int argc, - char *argv[]) { - GtkBuilder *builder; - GObject *window; - GObject *preview_box; +static int +config_ini_handler(void *user, const char *section, const char *name, + const char *value) { + if (strcmp(section, "preview") == 0) { + if (strcmp(name, "width") == 0) { + preview_width = strtol(value, NULL, 10); + } else if (strcmp(name, "height") == 0) { + preview_height = strtol(value, NULL, 10); + } else if (strcmp(name, "fmt") == 0) { + preview_fmt = strtol(value, NULL, 10); + } else { + g_printerr("Unknown key '%s' in [preview]", name); + exit(1); + } + } else if (strcmp(section, "device") == 0) { + if (strcmp(name, "rear") == 0) { + rear_dev_name = strdup(value); + } else if (strcmp(name, "front") == 0) { + front_dev_name = strdup(value); + } else { + g_printerr("Unknown key '%s' in [device]", name); + exit(1); + } + } else { + g_printerr("Unknown section '%s' in config file", section); + exit(1); + } + return 1; +} + +int +main(int argc, char *argv[]) +{ + if (argc != 2) { + g_printerr("Usage: camera configfile\n"); + return 1; + } + GError *error = NULL; - gtk_init(&argc, &argv); - - /* Construct a GtkBuilder instance and load our UI description */ - builder = gtk_builder_new(); + GtkBuilder *builder = gtk_builder_new(); if (gtk_builder_add_from_file(builder, "camera.glade", &error) == 0) { g_printerr("Error loading file: %s\n", error->message); g_clear_error(&error); return 1; } - /* Connect signal handlers to the constructed widgets. */ - window = gtk_builder_get_object(builder, "window"); - preview_box = gtk_builder_get_object(builder, "preview_box"); + GObject *window = gtk_builder_get_object(builder, "window"); + GObject *preview_box = gtk_builder_get_object(builder, "preview_box"); preview_image = gtk_builder_get_object(builder, "preview"); - g_signal_connect (window, "destroy", G_CALLBACK(gtk_main_quit), NULL); + g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL); - /* Load the css */ GtkCssProvider *provider = gtk_css_provider_new(); gtk_css_provider_load_from_path(provider, "camera.css", NULL); - GtkStyleContext *context; - context = gtk_widget_get_style_context(preview_box); - gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_USER); + GtkStyleContext *context = gtk_widget_get_style_context(preview_box); + gtk_style_context_add_provider(context, + GTK_STYLE_PROVIDER(provider), + GTK_STYLE_PROVIDER_PRIORITY_USER); - /* Grab a frame from the camera */ - int fd; - fd = open("/dev/video0", O_RDWR); - if (fd == -1) { - g_printerr("Error opening video device: /dev/video0\n"); + int result = ini_parse(argv[1], 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; } + + dev_name = rear_dev_name; + + int fd = open(dev_name, O_RDWR); + if (fd == -1) { + g_printerr("Error opening video device: %s\n", dev_name); + return 1; + } + init_device(fd); start_capturing(fd); get_frame(fd); - /* Show application */ gtk_widget_show(window); gtk_main(); - return 0; -} \ No newline at end of file +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..5c0149c --- /dev/null +++ b/meson.build @@ -0,0 +1,3 @@ +project('camera', 'c') +gtkdep = dependency('gtk+-3.0') +executable('camera', 'main.c', 'ini.c', dependencies : gtkdep) \ No newline at end of file diff --git a/uvc.ini b/uvc.ini new file mode 100644 index 0000000..035b88b --- /dev/null +++ b/uvc.ini @@ -0,0 +1,6 @@ +[device] +rear=/dev/video0 + +[preview] +width=640 +height=480 \ No newline at end of file