mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-18 02:55:46 +00:00
Initial commit
This commit is contained in:
commit
1ebe26fd08
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/src/*.o
|
||||||
|
/ustreamer
|
||||||
21
Makefile
Normal file
21
Makefile
Normal file
@ -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)
|
||||||
214
src/capture.c
Normal file
214
src/capture.c
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <linux/videodev2.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
6
src/capture.h
Normal file
6
src/capture.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "device.h"
|
||||||
|
|
||||||
|
|
||||||
|
void capture_loop(struct device *dev);
|
||||||
408
src/device.c
Normal file
408
src/device.c
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <linux/videodev2.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
52
src/device.h
Normal file
52
src/device.h
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <linux/videodev2.h>
|
||||||
|
|
||||||
|
|
||||||
|
#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);
|
||||||
108
src/main.c
Normal file
108
src/main.c
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
28
src/tools.c
Normal file
28
src/tools.c
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
36
src/tools.h
Normal file
36
src/tools.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
|
||||||
|
#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);
|
||||||
Loading…
x
Reference in New Issue
Block a user