commit 1ebe26fd0879c2632f1ef8e8a4da4a2ae0be623d Author: Devaev Maxim Date: Fri Sep 14 14:37:01 2018 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..200baf9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/src/*.o +/ustreamer diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c79bfcb --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +CC = gcc +CFLAGS = -c -Wall -Wextra -DDEBUG +LDFLAGS = +SOURCES = $(shell ls src/*.c) +OBJECTS = $(SOURCES:.c=.o) +PROG = ustreamer + + +all: $(SOURCES) $(PROG) + + +$(PROG): $(OBJECTS) + $(CC) $(LDFLAGS) $(OBJECTS) -o $@ + + +.c.o: + $(CC) $(CFLAGS) $< -o $@ + + +clean: + rm -f src/*.o $(PROG) diff --git a/src/capture.c b/src/capture.c new file mode 100644 index 0000000..2e8b049 --- /dev/null +++ b/src/capture.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include + +#include "device.h" +#include "tools.h" + + +static int _capture_init_loop(struct device *dev); +static int _capture_init(struct device *dev); +static int _capture_control(struct device *dev, const bool enable); +static int _capture_grab_buffer(struct device *dev, struct v4l2_buffer *buf); +static int _capture_release_buffer(struct device *dev, struct v4l2_buffer *buf); +static int _capture_handle_event(struct device *dev); + + +void capture_loop(struct device *dev) { + LOG_INFO("Using V4L2 device: %s", dev->path); + LOG_INFO("Using JPEG quality: %d%%", dev->jpeg_quality); + + while (_capture_init_loop(dev) == 0) { + int frames_count = 0; + + while (!(*dev->stop)) { + SEP_DEBUG('-'); + + fd_set read_fds; + fd_set write_fds; + fd_set error_fds; + + FD_ZERO(&read_fds); + FD_SET(dev->run->fd, &read_fds); + + FD_ZERO(&write_fds); + FD_SET(dev->run->fd, &write_fds); + + FD_ZERO(&error_fds); + FD_SET(dev->run->fd, &error_fds); + + struct timeval timeout; + timeout.tv_sec = dev->timeout; + timeout.tv_usec = 0; + + LOG_DEBUG("Calling select() on video device ..."); + int retval = select(dev->run->fd + 1, &read_fds, &write_fds, &error_fds, &timeout); + LOG_DEBUG("Device select() --> %d", retval); + + if (retval < 0) { + if (errno != EINTR) { + LOG_PERROR("Mainloop select() error"); + break; + } + + } else if (retval == 0) { + LOG_ERROR("Mainloop select() timeout"); + break; + + } else { + if (FD_ISSET(dev->run->fd, &read_fds)) { + LOG_DEBUG("Frame ready ..."); + + struct v4l2_buffer buf; + + if (_capture_grab_buffer(dev, &buf) < 0) { + break; + } + + if (dev->every_frame) { + if (frames_count < (int)dev->every_frame - 1) { + LOG_DEBUG("Dropping frame %d for option --every-frame=%d", frames_count + 1, dev->every_frame); + ++frames_count; + goto pass_frame; + } else { + frames_count = 0; + } + } + + // Workaround for broken, corrupted frames: + // Under low light conditions corrupted frames may get captured. + // The good thing is such frames are quite small compared to the regular pictures. + // For example a VGA (640x480) webcam picture is normally >= 8kByte large, + // corrupted frames are smaller. + if (buf.bytesused < dev->min_frame_size) { + LOG_DEBUG("Dropping too small frame sized %d bytes, assuming it as broken", buf.bytesused); + goto pass_frame; + } + + LOG_DEBUG("Grabbed a new frame"); + usleep(100000); // TODO: process dev->run->buffers[buf.index].start, buf.bytesused + + pass_frame: + + if (_capture_release_buffer(dev, &buf) < 0) { + break; + } + } + + if (FD_ISSET(dev->run->fd, &write_fds)) { + LOG_ERROR("Got unexpected writing event, seems device was disconnected"); + break; + } + + if (FD_ISSET(dev->run->fd, &error_fds)) { + LOG_INFO("Got V4L2 event"); + if (_capture_handle_event(dev) < 0) { + break; + } + } + } + } + } + _capture_control(dev, false); + device_close(dev); +} + +static int _capture_init_loop(struct device *dev) { + int retval = -1; + + while (!(*dev->stop)) { + if ((retval = _capture_init(dev)) < 0) { + LOG_INFO("Sleeping %d seconds before new capture init ...", dev->error_timeout); + sleep(dev->error_timeout); + } else { + break; + } + } + return retval; +} + +static int _capture_init(struct device *dev) { + SEP_INFO('='); + + _capture_control(dev, false); + device_close(dev); + + if (device_open(dev) < 0) { + goto error; + } + if (_capture_control(dev, true) < 0) { + goto error; + } + + return 0; + + error: + device_close(dev); + return -1; +} + +static int _capture_control(struct device *dev, const bool enable) { + if (enable != dev->run->capturing) { + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + LOG_DEBUG("Calling ioctl(%s) ...", (enable ? "VIDIOC_STREAMON" : "VIDIOC_STREAMOFF")); + if (xioctl(dev->run->fd, (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) { + LOG_PERROR("Unable to %s capturing", (enable ? "start" : "stop")); + if (enable) { + return -1; + } + } + + dev->run->capturing = enable; + LOG_INFO("Capturing %s", (enable ? "started" : "stopped")); + } + return 0; +} + +static int _capture_grab_buffer(struct device *dev, struct v4l2_buffer *buf) { + memset(buf, 0, sizeof(struct v4l2_buffer)); + buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf->memory = V4L2_MEMORY_MMAP; + + LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ..."); + if (xioctl(dev->run->fd, VIDIOC_DQBUF, buf) < 0) { + LOG_PERROR("Unable to dequeue buffer"); + return -1; + } + + LOG_DEBUG("Got a new frame in buffer index=%d; bytesused=%d", buf->index, buf->bytesused); + if (buf->index >= dev->run->n_buffers) { + LOG_ERROR("Got invalid buffer index=%d; nbuffers=%d", buf->index, dev->run->n_buffers); + return -1; + } + return 0; +} + +static int _capture_release_buffer(struct device *dev, struct v4l2_buffer *buf) { + LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) ..."); + if (xioctl(dev->run->fd, VIDIOC_QBUF, buf) < 0) { + LOG_PERROR("Unable to requeue buffer"); + return -1; + } + return 0; +} + +static int _capture_handle_event(struct device *dev) { + struct v4l2_event event; + + LOG_DEBUG("Calling ioctl(VIDIOC_DQEVENT) ..."); + if (!xioctl(dev->run->fd, VIDIOC_DQEVENT, &event)) { + switch (event.type) { + case V4L2_EVENT_SOURCE_CHANGE: + LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed"); + return -1; + case V4L2_EVENT_EOS: + LOG_INFO("Got V4L2_EVENT_EOS: end of stream (ignored)"); + return 0; + } + } else { + LOG_ERROR("Got some V4L2 device event, but where is it? "); + } + return 0; +} diff --git a/src/capture.h b/src/capture.h new file mode 100644 index 0000000..91502d1 --- /dev/null +++ b/src/capture.h @@ -0,0 +1,6 @@ +#pragma once + +#include "device.h" + + +void capture_loop(struct device *dev); diff --git a/src/device.c b/src/device.c new file mode 100644 index 0000000..17b2652 --- /dev/null +++ b/src/device.c @@ -0,0 +1,408 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "device.h" +#include "tools.h" + + +static const char DEFAULT_DEVICE[] = "/dev/video0"; + +static const struct { + const char *name; + const v4l2_std_id standard; +} STANDARDS[] = { + {"UNKNOWN", V4L2_STD_UNKNOWN}, + {"PAL", V4L2_STD_PAL}, + {"NTSC", V4L2_STD_NTSC}, + {"SECAM", V4L2_STD_SECAM} +}; + +static const struct { + const char *name; + const unsigned format; +} FORMATS[] = { + {"YUYV", V4L2_PIX_FMT_YUYV}, + {"UYVY", V4L2_PIX_FMT_UYVY}, + {"RGB565", V4L2_PIX_FMT_RGB565} +}; + + +static int _device_open_check_cap(struct device *dev); +static int _device_open_dv_timings(struct device *dev); +static int _device_apply_dv_timings(struct device *dev); +static int _device_open_format(struct device *dev); +static int _device_open_mmap(struct device *dev); +static int _device_open_queue_buffers(struct device *dev); + +static const char *_format_to_string_auto(char *buf, const size_t length, const unsigned format); +static const char *_format_to_string_null(const unsigned format); +static const char *_standard_to_string(const v4l2_std_id standard); + + +void device_init(struct device *dev, struct device_runtime *run, bool *const stop) { + LOG_DEBUG("Initializing a new device struct ..."); + + memset(dev, 0, sizeof(struct device)); + memset(run, 0, sizeof(struct device_runtime)); + + dev->path = (char *)DEFAULT_DEVICE; + dev->width = 640; + dev->height = 480; + dev->format = V4L2_PIX_FMT_YUYV; + dev->standard = V4L2_STD_UNKNOWN; + dev->n_buffers = 4; + dev->jpeg_quality = 80; + dev->timeout = 1; + dev->error_timeout = 1; + + dev->run = run; + dev->run->fd = -1; + dev->stop = stop; + LOG_DEBUG("We have a clear device!"); +} + +int device_parse_format(const char *const str) { + for (unsigned index = 0; index < sizeof(FORMATS) / sizeof(FORMATS[0]); ++index) { + if (!strcasecmp(str, FORMATS[index].name)) { + return FORMATS[index].format; + } + } + return FORMAT_UNKNOWN; +} + +v4l2_std_id device_parse_standard(const char *const str) { + for (unsigned index = 1; index < sizeof(STANDARDS) / sizeof(STANDARDS[0]); ++index) { + if (!strcasecmp(str, STANDARDS[index].name)) { + return STANDARDS[index].standard; + } + } + return STANDARD_UNKNOWN; +} + +int device_open(struct device *dev) { + if ((dev->run->fd = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) { + LOG_PERROR("Can't open device"); + goto error; + } + LOG_INFO("Device fd=%d opened", dev->run->fd); + + if (_device_open_check_cap(dev) < 0) { + goto error; + } + if (_device_open_dv_timings(dev) < 0) { + goto error; + } + if (_device_open_format(dev) < 0) { + goto error; + } + if (_device_open_mmap(dev) < 0) { + goto error; + } + if (_device_open_queue_buffers(dev) < 0) { + goto error; + } + + LOG_DEBUG("Device fd=%d initialized", dev->run->fd); + return 0; + + error: + device_close(dev); + return -1; +} + +void device_close(struct device *dev) { + if (dev->run->buffers) { + LOG_DEBUG("Unmapping buffers ..."); + for (unsigned index = 0; index < dev->run->n_buffers; ++index) { + if (dev->run->buffers[index].start != MAP_FAILED) { + if (munmap(dev->run->buffers[index].start, dev->run->buffers[index].length) < 0) { + LOG_PERROR("Can't unmap buffer %d", index); + } + } + } + dev->run->n_buffers = 0; + free(dev->run->buffers); + dev->run->buffers = NULL; + } + + if (dev->run->fd >= 0) { + LOG_DEBUG("Closing device ..."); + if (close(dev->run->fd) < 0) { + LOG_PERROR("Can't close device fd=%d", dev->run->fd); + } else { + LOG_INFO("Device fd=%d closed", dev->run->fd); + } + dev->run->fd = -1; + } +} + +static int _device_open_check_cap(struct device *dev) { + struct v4l2_capability cap; + + MEMSET_ZERO(cap); + + LOG_DEBUG("Calling ioctl(VIDIOC_QUERYCAP) ..."); + if (xioctl(dev->run->fd, VIDIOC_QUERYCAP, &cap) < 0) { + LOG_PERROR("Can't query device (VIDIOC_QUERYCAP)"); + return -1; + } + + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + LOG_ERROR("Video capture not supported by our device"); + return -1; + } + + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + LOG_ERROR("Device does not support streaming IO"); + return -1; + } + + if (dev->standard != V4L2_STD_UNKNOWN) { + LOG_INFO("Using TV standard: %s", _standard_to_string(dev->standard)); + if (xioctl(dev->run->fd, VIDIOC_S_STD, &dev->standard) < 0) { + LOG_PERROR("Can't set video standard"); + return -1; + } + } else { + LOG_INFO("Using TV standard: DEFAULT"); + } + return 0; +} + +static int _device_open_dv_timings(struct device *dev) { + if (dev->dv_timings) { + LOG_DEBUG("Using DV-timings"); + + if (_device_apply_dv_timings(dev) < 0) { + return -1; + } + + struct v4l2_event_subscription sub; + + MEMSET_ZERO(sub); + sub.type = V4L2_EVENT_SOURCE_CHANGE; + + LOG_DEBUG("Calling ioctl(VIDIOC_SUBSCRIBE_EVENT) ..."); + if (xioctl(dev->run->fd, VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) { + LOG_PERROR("Can't subscribe to V4L2_EVENT_SOURCE_CHANGE"); + return -1; + } + + } else { + dev->run->width = dev->width; + dev->run->height = dev->height; + } + return 0; +} + +static int _device_apply_dv_timings(struct device *dev) { + struct v4l2_dv_timings dv_timings; + + MEMSET_ZERO(dv_timings); + + LOG_DEBUG("Calling ioctl(VIDIOC_QUERY_DV_TIMINGS) ..."); + if (xioctl(dev->run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv_timings) == 0) { + LOG_INFO( + "Got new DV timings: resolution=%dx%d; pixclk=%llu\n", + dv_timings.bt.width, + dv_timings.bt.height, + dv_timings.bt.pixelclock + ); + + LOG_DEBUG("Calling ioctl(VIDIOC_S_DV_TIMINGS) ..."); + if (xioctl(dev->run->fd, VIDIOC_S_DV_TIMINGS, &dv_timings) < 0) { + LOG_PERROR("Failed to set DV timings"); + return -1; + } + + dev->run->width = dv_timings.bt.width; + dev->run->height = dv_timings.bt.height; + + } else { + LOG_DEBUG("Calling ioctl(VIDIOC_QUERYSTD) ..."); + if (xioctl(dev->run->fd, VIDIOC_QUERYSTD, &dev->standard) == 0) { + LOG_INFO("Applying the new VIDIOC_S_STD: %s ...", _standard_to_string(dev->standard)); + if (xioctl(dev->run->fd, VIDIOC_S_STD, &dev->standard) < 0) { + LOG_PERROR("Can't set video standard"); + return -1; + } + } + } + return 0; +} + +static int _device_open_format(struct device *dev) { + struct v4l2_format fmt; + + MEMSET_ZERO(fmt); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = dev->run->width; + fmt.fmt.pix.height = dev->run->height; + fmt.fmt.pix.pixelformat = dev->format; + fmt.fmt.pix.field = V4L2_FIELD_ANY; + + // Set format + LOG_DEBUG("Calling ioctl(VIDIOC_S_FMT) ..."); + if (xioctl(dev->run->fd, VIDIOC_S_FMT, &fmt) < 0) { + char format_str[8]; + + LOG_PERROR( + "Unable to set format=%s; resolution=%dx%d", + _format_to_string_auto(format_str, 8, dev->format), + dev->run->width, + dev->run->height + ); + return -1; + } + + // Check resolution + if (fmt.fmt.pix.width != dev->run->width || fmt.fmt.pix.height != dev->run->height) { + LOG_ERROR("Requested resolution=%dx%d is unavailable", dev->run->width, dev->run->height); + } + dev->run->width = fmt.fmt.pix.width; + dev->run->height = fmt.fmt.pix.height; + LOG_INFO("Using resolution: %dx%d", dev->run->width, dev->run->height); + + // Check format + if (fmt.fmt.pix.pixelformat != dev->format) { + char format_requested_str[8]; + char format_obtained_str[8]; + char *format_str_nullable; + + LOG_ERROR( + "Could not obtain the requested pixelformat=%s; driver gave us %s", + _format_to_string_auto(format_requested_str, 8, dev->format), + _format_to_string_auto(format_obtained_str, 8, fmt.fmt.pix.pixelformat) + ); + + if ((format_str_nullable = (char *)_format_to_string_null(fmt.fmt.pix.pixelformat)) != NULL) { + LOG_INFO( + "Falling back to %s mode (consider using '--format=%s' option)", + format_str_nullable, + format_str_nullable + ); + } else { + LOG_ERROR("Unsupported pixel format"); + return -1; + } + } + dev->run->format = fmt.fmt.pix.pixelformat; + return 0; +} + +static int _device_open_mmap(struct device *dev) { + struct v4l2_requestbuffers req; + + MEMSET_ZERO(req); + req.count = dev->n_buffers; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + LOG_DEBUG("Calling ioctl(VIDIOC_REQBUFS) ..."); + if (xioctl(dev->run->fd, VIDIOC_REQBUFS, &req)) { + LOG_PERROR("Device '%s' doesn't support memory mapping", dev->path); + return -1; + } + + if (req.count < 1) { + LOG_ERROR("Insufficient buffer memory: %d", req.count); + return -1; + } else { + LOG_INFO("Requested %d device buffers, got %d", dev->n_buffers, req.count); + } + + LOG_DEBUG("Allocating buffers ..."); + dev->run->buffers = NULL; + if ((dev->run->buffers = calloc(req.count, sizeof(*dev->run->buffers))) == NULL) { + LOG_PERROR("Can't allocate buffers"); + return -1; + } + + for (dev->run->n_buffers = 0; dev->run->n_buffers < req.count; ++dev->run->n_buffers) { + struct v4l2_buffer buf; + + MEMSET_ZERO(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = dev->run->n_buffers; + + LOG_DEBUG("Calling ioctl(VIDIOC_QUERYBUF) for buffer %d ...", dev->run->n_buffers); + if (xioctl(dev->run->fd, VIDIOC_QUERYBUF, &buf) < 0) { + LOG_PERROR("Can't VIDIOC_QUERYBUF"); + return -1; + } + + LOG_DEBUG("Mapping buffer %d ...", dev->run->n_buffers); + dev->run->buffers[dev->run->n_buffers].length = buf.length; + dev->run->buffers[dev->run->n_buffers].start = mmap(NULL, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, dev->run->fd, buf.m.offset); + if (dev->run->buffers[dev->run->n_buffers].start == MAP_FAILED) { + LOG_PERROR("Can't map buffer %d", dev->run->n_buffers); + return -1; + } + } + return 0; +} + +static int _device_open_queue_buffers(struct device *dev) { + for (unsigned index = 0; index < dev->run->n_buffers; ++index) { + struct v4l2_buffer buf; + + MEMSET_ZERO(buf); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = index; + + LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) for buffer %d ...", index); + if (xioctl(dev->run->fd, VIDIOC_QBUF, &buf) < 0) { + LOG_PERROR("Can't VIDIOC_QBUF"); + return -1; + } + } + return 0; +} + +static const char *_format_to_string_auto(char *buf, const size_t length, const unsigned format) { + if (length < 8) { + buf[0] = '\0'; + } else { + buf[0] = format & 0x7f; + buf[1] = (format >> 8) & 0x7f; + buf[2] = (format >> 16) & 0x7f; + buf[3] = (format >> 24) & 0x7f; + if (format & (1 << 31)) { + buf[4] = '-'; + buf[5] = 'B'; + buf[6] = 'E'; + buf[7] = '\0'; + } else { + buf[4] = '\0'; + } + } + return buf; +} + +static const char *_format_to_string_null(const unsigned format) { + for (unsigned index = 0; index < sizeof(FORMATS) / sizeof(FORMATS[0]); ++index) { + if (format == FORMATS[index].format) { + return FORMATS[index].name; + } + } + return NULL; +} + +static const char *_standard_to_string(v4l2_std_id standard) { + for (unsigned index = 0; index < sizeof(STANDARDS) / sizeof(STANDARDS[0]); ++index) { + if (standard == STANDARDS[index].standard) { + return STANDARDS[index].name; + } + } + return STANDARDS[0].name; +} diff --git a/src/device.h b/src/device.h new file mode 100644 index 0000000..2ce2f5e --- /dev/null +++ b/src/device.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + + +#define FORMAT_UNKNOWN -1 +#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN + + +struct buffer { + void *start; + size_t length; +}; + +struct device_runtime { + int fd; + unsigned width; + unsigned height; + unsigned format; + unsigned n_buffers; + struct buffer *buffers; + bool capturing; +}; + +struct device { + char *path; + unsigned width; + unsigned height; + unsigned format; + v4l2_std_id standard; + bool dv_timings; + unsigned n_buffers; + unsigned every_frame; + unsigned min_frame_size; + unsigned jpeg_quality; + unsigned timeout; + unsigned error_timeout; + + struct device_runtime *run; + bool *stop; +}; + + +void device_init(struct device *dev, struct device_runtime *run, bool *const stop); + +int device_parse_format(const char *const str); +v4l2_std_id device_parse_standard(const char *const str); + +int device_open(struct device *dev); +void device_close(struct device *dev); diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..c34c2de --- /dev/null +++ b/src/main.c @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "device.h" +#include "capture.h" +#include "tools.h" + + +static const char _short_opts[] = "hd:f:s:e:tb:q:"; +static const struct option _long_opts[] = { + {"help", no_argument, NULL, 'h'}, + {"device", required_argument, NULL, 'd'}, + {"format", required_argument, NULL, 'f'}, + {"tv-standard", required_argument, NULL, 's'}, + {"every-frame", required_argument, NULL, 'e'}, + {"min-frame-size", required_argument, NULL, 'z'}, + {"dv-timings", no_argument, NULL, 't'}, + {"buffers", required_argument, NULL, 'b'}, + {"jpeg-quality", required_argument, NULL, 'q'}, + {"width", required_argument, NULL, 1000}, + {"height", required_argument, NULL, 1001}, + {"v4l2-timeout", required_argument, NULL, 1002}, + {"v4l2-error-timeout", required_argument, NULL, 1003}, + {NULL, 0, NULL, 0}, +}; + + +static void _help(int exit_code) { + printf("No manual yet\n"); + exit(exit_code); +} + +static void _parse_options(int argc, char *argv[], struct device *dev) { +# define OPT_ARG(_dest) \ + { _dest = optarg; break; } + +# define OPT_TRUE(_dest) \ + { _dest = true; break; } + +# define OPT_UNSIGNED(_dest, _name) \ + { int _tmp = strtol(optarg, NULL, 0); \ + if (errno || _tmp < 0) \ + { printf("Invalid value for: %s\n", _name); exit(EXIT_FAILURE); } \ + _dest = _tmp; break; } + +# define OPT_PARSE(_dest, _func, _invalid, _name) \ + { if ((_dest = _func(optarg)) == _invalid) \ + { printf("Unknown " _name ": %s\n", optarg); exit(EXIT_FAILURE); } \ + break; } + + int index; + int ch; + + LOG_DEBUG("Parsing CLI options ..."); + while ((ch = getopt_long(argc, argv, _short_opts, _long_opts, &index)) >= 0) { + switch (ch) { + case 0: break; + case 'd': OPT_ARG(dev->path); +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic push + case 'f': OPT_PARSE(dev->format, device_parse_format, FORMAT_UNKNOWN, "pixel format"); +# pragma GCC diagnostic pop + case 's': OPT_PARSE(dev->standard, device_parse_standard, STANDARD_UNKNOWN, "TV standard"); + case 'e': OPT_UNSIGNED(dev->every_frame, "--every-frame"); + case 'z': OPT_UNSIGNED(dev->min_frame_size, "--min-frame-size"); + case 't': OPT_TRUE(dev->dv_timings); + case 'b': OPT_UNSIGNED(dev->n_buffers, "--buffers"); + case 'q': OPT_UNSIGNED(dev->jpeg_quality, "--jpeg-quality"); + case 1000: OPT_UNSIGNED(dev->width, "--width"); + case 1001: OPT_UNSIGNED(dev->height, "--height"); + case 1002: OPT_UNSIGNED(dev->timeout, "--timeout"); + case 1003: OPT_UNSIGNED(dev->error_timeout, "--error-timeout"); + case 'h': _help(EXIT_SUCCESS); break; + default: _help(EXIT_FAILURE); break; + } + } +} + + +static bool _global_stop = false; + +static void _interrupt_handler(int signum) { + LOG_INFO("===== Stopping by %s =====", strsignal(signum)); + _global_stop = true; +} + + +int main(int argc, char *argv[]) { + struct device dev; + struct device_runtime run; + + device_init(&dev, &run, &_global_stop); + _parse_options(argc, argv, &dev); + + LOG_INFO("Installing SIGINT handler ..."); + signal(SIGINT, _interrupt_handler); + + LOG_INFO("Installing SIGTERM handler ..."); + signal(SIGTERM, _interrupt_handler); + + capture_loop(&dev); + return 0; +} diff --git a/src/tools.c b/src/tools.c new file mode 100644 index 0000000..13abc9b --- /dev/null +++ b/src/tools.c @@ -0,0 +1,28 @@ +#include +#include +#include + +#include "tools.h" + + +int xioctl(const int fd, const int request, void *arg) { + int retries = XIOCTL_RETRIES; + int retval = -1; + + do { + retval = ioctl(fd, request, arg); + } while ( + retval + && retries-- + && ( + errno == EINTR + || errno == EAGAIN + || errno == ETIMEDOUT + ) + ); + + if (retval && retries <= 0) { + LOG_PERROR("ioctl(%d) retried %d times; giving up", request, XIOCTL_RETRIES); + } + return retval; +} diff --git a/src/tools.h b/src/tools.h new file mode 100644 index 0000000..ee95bbb --- /dev/null +++ b/src/tools.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + + +#define SEP_INFO(_x_ch) \ + { for (int _i = 0; _i < 80; ++_i) putchar(_x_ch); putchar('\n'); } + +#define LOG_INFO(_x_msg, ...) \ + printf("-- INFO -- " _x_msg "\n", ##__VA_ARGS__) + +#ifdef DEBUG +# define LOG_DEBUG(_x_msg, ...) \ + printf(" DEBUG -- " _x_msg "\n", ##__VA_ARGS__) +# define SEP_DEBUG(_x_ch) \ + SEP_INFO(_x_ch) +#else +# define LOG_DEBUG(...) +# define SEP_DEBUG(_x_ch) +#endif + +#define LOG_ERROR(_x_msg, ...) \ + printf("** ERROR -- " _x_msg "\n", ##__VA_ARGS__) + +#define LOG_PERROR(_x_msg, ...) \ + printf("** ERROR -- " _x_msg ": %s\n", ##__VA_ARGS__, strerror(errno)) + + +#define MEMSET_ZERO(_x_obj) memset(&(_x_obj), 0, sizeof(_x_obj)) + +#define XIOCTL_RETRIES 4 + + +int xioctl(const int fd, const int request, void *arg);