summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlastair Poole <netstar@gmail.com>2020-07-15 14:10:20 +0100
committerAlastair Poole <netstar@gmail.com>2020-07-15 14:10:20 +0100
commite002da570ebde6c79da4c8fd1387b537f0e35749 (patch)
treeebae6089557ef3ad5ca2751efbcf827af3bdf495
Record to images/ in jpeg.
-rw-r--r--README.md12
-rw-r--r--cam.c305
-rw-r--r--images/.keepmealive0
-rw-r--r--makefile8
4 files changed, 325 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..045480e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,12 @@
+# OpenBSD Video4Linux2 API
+
+As far as I can tell, as of OpenBSD 6.7 the only streaming capabilities
+supported are read(2) and mmap(2) based.
+
+Camera I have outputs in YUV and using this to save files in jpeg using
+EFL on OpenBSD. See https://enlightenment.org.
+
+Yes, OpenBSD and EFL and Enlightenment is a viable choice on arm64 and
+amd64 etc for all sorts of projects.
+
+Testing this on rpi4 (arm64).
diff --git a/cam.c b/cam.c
new file mode 100644
index 0000000..d09eb8d
--- /dev/null
+++ b/cam.c
@@ -0,0 +1,305 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/videoio.h>
+
+#include <Eina.h>
+#include <Ecore.h>
+#include <Evas.h>
+#include <Ecore_Evas.h>
+
+#define RGB_VALID(x) ((x) < 0)? 0 :(((x)>255)? 255: (x))
+
+static void
+borked(const char *why)
+{
+ fprintf(stderr, "ERROR: %s\n", why);
+ exit(1);
+}
+
+static void
+yuvtorgba(int Y, int U, int V, char *rgb)
+{
+ int r, g, b;
+
+ r = 1.164*(Y-16) + 1.596*(V-128);
+ g = 1.164*(Y-16) + (-0.813*(U-128)) + (-0.391*(V-128));
+ b = 1.164*(Y-16) + 2.018*(U-128);
+ rgb[0] = RGB_VALID(b);
+ rgb[1] = RGB_VALID(g);
+ rgb[2] = RGB_VALID(r);
+ rgb[3] = 0;
+}
+
+void
+yuv_convert(char *buf, char *rgb, int x, int y)
+{
+ for (int i = 0; i < x * y; i += 2) {
+ int Y1, Y2, U, V;
+
+ Y1 = buf[2*i+0];
+ Y2 = buf[2*i+2];
+ U = buf[2*i+1];
+ V = buf[2*i+3];
+
+ yuvtorgba(Y1, U, V, &rgb[4*i]);
+ yuvtorgba(Y2, U, V, &rgb[4*(i+1)]);
+ }
+}
+
+static void
+save_photo(const char *data, unsigned int w, unsigned int h)
+{
+ Ecore_Evas *ee;
+ Evas *evas;
+ Evas_Object *o;
+ struct tm *info;
+ time_t rawtime;
+ char buf[256];
+ char filename[4096];
+ struct timespec ts;
+
+ time(&rawtime);
+ info = localtime(&rawtime);
+ strftime(buf, sizeof(buf), "%F-%T", info);
+ clock_gettime(CLOCK_REALTIME, &ts);
+ snprintf(filename, sizeof(filename), "images/%s:%ld.jpg", buf, ts.tv_nsec);
+
+ ee = ecore_evas_new(NULL, 0, 0, 1, 1, NULL);
+ evas = ecore_evas_get(ee);
+ o = evas_object_image_filled_add(evas);
+ evas_object_image_size_set(o, w, h);
+ evas_object_image_colorspace_set(o, EVAS_COLORSPACE_ARGB8888);
+ evas_object_image_data_set(o, (void *)data);
+ evas_object_resize(o, w, h);
+ evas_object_move(o, 0, 0);
+ evas_object_image_save(o, filename, NULL, NULL);
+ evas_object_del(o);
+ ecore_evas_free(ee);
+}
+
+typedef struct {
+ char *start;
+ uint32_t length;
+} buffer_t;
+
+static int
+mmap_mjpeg(int fd, int w, int h)
+{
+ fd_set fds;
+ struct timeval tv;
+ struct v4l2_buffer buf;
+ struct v4l2_requestbuffers req;
+ int i, type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ memset(&req, 0, sizeof(req));
+ req.count = 10;
+ req.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ req.memory = V4L2_MEMORY_MMAP;
+
+ if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
+ borked("VIDIO_REQBUFS");
+ }
+
+ buffer_t *buffers = calloc(req.count, sizeof(buffer_t));
+ if (!buffers) {
+ borked("calloc()");
+ }
+
+ for (i = 0; i < req.count; i++) {
+ memset(&buf, 0, sizeof(buf));
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = i;
+
+ if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
+ borked("VIDIOC_QUERYBUF");
+ }
+
+ buffers[i].length = buf.length;
+ buffers[i].start = mmap(NULL, buf.length, PROT_READ, MAP_SHARED, fd, buf.m.offset);
+ if (buffers[i].start == MAP_FAILED) {
+ borked("mmap");
+ }
+ }
+
+ for (i = 0; i < req.count; i++) {
+ memset(&buf, 0, sizeof(buf));
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = i;
+
+ if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
+ borked("VIDIOC_QBUF");
+ }
+ }
+
+ if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {
+ borked("VIDIOC_STREAMON");
+ }
+
+ char *out = malloc((w * h) * sizeof(uint32_t));
+ if (!out) {
+ borked("malloc");
+ }
+
+ for (i = 0; i < 1024; i++) {
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+
+ int status = select(fd + 1, &fds, NULL, NULL, NULL);
+ if (status == -1 && errno == EINTR) continue;
+ else if (status == 0)
+ borked("timeout");
+
+ memset(&buf, 0, sizeof(buf));
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+
+ if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
+ switch (errno) {
+ case EAGAIN:
+ continue;
+ default:
+ borked("VIDIO_DQBUF");
+ }
+ }
+
+ yuv_convert(buffers[buf.index].start, out, w, h);
+ save_photo(out, w, h);
+
+ for (i = 0; i < req.count; i++) {
+ memset(&buf, 0, sizeof(buf));
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = i;
+
+ if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
+ borked("VIDIOC_QBUF");
+ }
+ }
+ }
+
+ free(out);
+
+ type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) {
+ borked("VIDIOC_STREAMON");
+ }
+
+ for (i = 0; i < req.count; i++) {
+ munmap(buffers[i].start, buffers[i].length);
+ }
+
+ return 0;
+}
+
+static int
+read_mjpeg(int fd, int w, int h)
+{
+ char *out, *data;
+ fd_set fds;
+ struct timeval tv;
+ int i, status;
+
+ out = malloc((w * h) * sizeof(uint32_t));
+ if (!out) {
+ borked("malloc()");
+ }
+ data = malloc(w * h * 2);
+ if (!data) {
+ borked("malloc()");
+ }
+
+ for (i = 0; i < 128; i++) {
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+
+ tv.tv_sec = 2;
+ tv.tv_usec = 0;
+
+ status = select(fd + 1, &fds, NULL, NULL, &tv);
+ if (status == -1 && errno == EINTR) continue;
+ else if (status == 0)
+ borked("timeout");
+
+ if ((read(fd, data, (w * h) * 2)) == -1) {
+ printf("%s\n", strerror(errno));
+ break;
+ }
+ yuv_convert(data, out, w, h);
+ save_photo(out, w, h);
+ }
+
+ free(out);
+ free(data);
+
+ return 0;
+}
+
+int main(void)
+{
+ struct v4l2_capability cap;
+ struct v4l2_cropcap cropcap;
+ struct v4l2_format format;
+ int fd, ret = 1;
+ uint32_t w, h;
+
+ ecore_evas_init();
+
+ fd = open("/dev/video0", O_RDWR | O_NONBLOCK);
+ if (fd == -1) return 1;
+
+ if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
+ goto cleanup;
+ }
+
+ if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
+ borked("NO STREAMING CAPABILITY");
+ }
+
+ memset(&format, 0, sizeof(format));
+ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ if (ioctl(fd, VIDIOC_G_FMT, &format) == -1) {
+ printf("%s\n", strerror(errno));
+ goto cleanup;
+ }
+
+ w = format.fmt.pix.width = 1920;
+ h = format.fmt.pix.height = 1080;
+
+ format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
+
+ if (ioctl(fd, VIDIOC_S_FMT, &format) == -1) {
+ printf("%s\n", strerror(errno));
+ goto cleanup;
+ }
+ if (ioctl(fd, VIDIOC_G_FMT, &format) == -1) {
+ printf("%s\n", strerror(errno));
+ goto cleanup;
+ }
+
+ switch (format.fmt.pix.pixelformat) {
+ case V4L2_PIX_FMT_YUYV:
+ read_mjpeg(fd, w, h);
+ //mmap_mjpeg(fd, w, h);
+ break;
+ default:
+ break;
+ }
+
+cleanup:
+ close(fd);
+ ecore_evas_shutdown();
+
+ return ret;
+}
diff --git a/images/.keepmealive b/images/.keepmealive
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/images/.keepmealive
diff --git a/makefile b/makefile
new file mode 100644
index 0000000..0de8e8b
--- /dev/null
+++ b/makefile
@@ -0,0 +1,8 @@
+FLAGS=`pkg-config --libs --cflags evas ecore-evas`
+
+OUT=cam
+
+default:
+ $(CC) $(FLAGS) -O2 $(OUT).c -o $(OUT)
+clean:
+ -rm $(OUT)