This commit is contained in:
Kamil Trzcinski 2022-04-04 13:51:29 +02:00
commit d5fe55d2e0
12 changed files with 762 additions and 0 deletions

2
Makefile Normal file
View File

@ -0,0 +1,2 @@
camera_stream: *.c *.h
gcc -o camera_stream *.c

72
buffer.c Normal file
View File

@ -0,0 +1,72 @@
#include "buffer.h"
#include "buffer_list.h"
#include "device.h"
buffer_t *buffer_open(const char *name, buffer_list_t *buf_list, int index) {
buffer_t *buf = calloc(1, sizeof(buffer_t));
device_t *dev = buf_list->device;
buf->name = strdup(name);
buf->index = index;
buf->buf_list = buf_list;
buf->dma_fd = -1;
if (buf_list->do_mmap) {
buf->v4l2_buffer.type = buf_list->type;
buf->v4l2_buffer.memory = V4L2_MEMORY_MMAP;
buf->v4l2_buffer.index = index;
if (buf_list->do_mplanes) {
buf->v4l2_buffer.length = 1;
buf->v4l2_buffer.m.planes = &buf->v4l2_plane;
}
E_XIOCTL(buf_list, dev->fd, VIDIOC_QUERYBUF, &buf->v4l2_buffer, "Cannot query buffer %d", index);
if (buf_list->do_mplanes) {
buf->offset = buf->v4l2_plane.m.mem_offset;
buf->length = buf->v4l2_plane.length;
} else {
buf->offset = buf->v4l2_buffer.m.offset;
buf->length = buf->v4l2_buffer.length;
}
buf->start = mmap(NULL, buf->length, PROT_READ | PROT_WRITE, MAP_SHARED, dev->fd, buf->offset);
if (buf->start == MAP_FAILED) {
goto error;
}
}
if (buf_list->do_dma) {
struct v4l2_exportbuffer v4l2_exp = {0};
v4l2_exp.type = buf_list->type;
v4l2_exp.index = index;
v4l2_exp.plane = 0;
E_XIOCTL(buf_list, dev->fd, VIDIOC_EXPBUF, &v4l2_exp, "Can't export queue buffer=%u to DMA", index);
buf->dma_fd = v4l2_exp.fd;
}
buffer_capture_enqueue(buf);
return buf;
error:
buffer_close(buf);
return NULL;
}
void buffer_close(buffer_t *buf)
{
if (buf == NULL) {
return;
}
if (buf->start && buf->start != MAP_FAILED) {
munmap(buf->start, buf->length);
}
if (buf->dma_fd >= 0) {
close(buf->dma_fd);
}
free(buf->name);
free(buf);
}

21
buffer.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include "v4l2.h"
typedef struct buffer_s {
char *name;
struct buffer_list_s *buf_list;
int index;
void *start;
size_t offset;
size_t length;
struct v4l2_buffer v4l2_buffer;
struct v4l2_plane v4l2_plane;
int dma_fd;
bool enqueued;
} buffer_t;
buffer_t *buffer_open(const char *name, struct buffer_list_s *buf_list, int buffer);
void buffer_close(buffer_t *buf);
bool buffer_output_dequeue(buffer_t *buf);
bool buffer_capture_enqueue(buffer_t *buf);

158
buffer_list.c Normal file
View File

@ -0,0 +1,158 @@
#include "buffer.h"
#include "buffer_list.h"
#include "device.h"
buffer_list_t *buffer_list_open(const char *name, struct device_s *dev, unsigned type)
{
buffer_list_t *buf_list = calloc(1, sizeof(buffer_list_t));
buf_list->device = dev;
buf_list->name = strdup(name);
buf_list->type = type;
switch(type) {
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
break;
case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
buf_list->do_mplanes = true;
break;
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
buf_list->do_dma = true;
buf_list->do_mmap = true;
buf_list->do_capture = true;
break;
case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
buf_list->do_dma = true;
buf_list->do_mmap = true;
buf_list->do_mplanes = true;
buf_list->do_capture = true;
break;
default:
E_LOG_PERROR(buf_list, "Unknown type=%d", type);
goto error;
}
return buf_list;
error:
buffer_list_close(buf_list);
return NULL;
}
void buffer_list_close(buffer_list_t *buf_list)
{
if (!buf_list) {
return;
}
if (buf_list->bufs) {
for (int i = 0; i < buf_list->nbufs; i++) {
buffer_close(buf_list->bufs[i]);
}
free(buf_list->bufs);
buf_list->bufs = NULL;
buf_list->nbufs = 0;
}
free(buf_list->name);
free(buf_list);
}
int buffer_list_set_format(buffer_list_t *buf_list, unsigned width, unsigned height, unsigned format)
{
struct v4l2_format *fmt = &buf_list->v4l2_format;
fmt->type = buf_list->type;
if (buf_list->do_mplanes) {
fmt->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
fmt->fmt.pix.width = width;
fmt->fmt.pix.height = height;
fmt->fmt.pix.pixelformat = format;
fmt->fmt.pix.field = V4L2_FIELD_ANY;
fmt->fmt.pix.bytesperline = fourcc_to_stride(width, format);
} else {
fmt->fmt.pix_mp.colorspace = V4L2_COLORSPACE_JPEG;
fmt->fmt.pix_mp.width = width;
fmt->fmt.pix_mp.height = height;
fmt->fmt.pix_mp.pixelformat = format;
fmt->fmt.pix_mp.field = V4L2_FIELD_ANY;
fmt->fmt.pix_mp.num_planes = 1;
fmt->fmt.pix_mp.plane_fmt[0].bytesperline = fourcc_to_stride(width, format);
}
E_LOG_DEBUG(buf_list, "Configuring format ...");
E_XIOCTL(buf_list, buf_list->device->fd, VIDIOC_S_FMT, fmt, "Can't set format");
if (fmt->fmt.pix.width != width || fmt->fmt.pix.height != height) {
E_LOG_ERROR(buf_list, "Requested resolution=%ux%u is unavailable. Got %ux%u.",
width, height, fmt->fmt.pix.width, fmt->fmt.pix.height);
}
if (fmt->fmt.pix.pixelformat != format) {
E_LOG_ERROR(buf_list, "Could not obtain the requested format=%s; driver gave us %s",
fourcc_to_string(format).buf,
fourcc_to_string(fmt->fmt.pix.pixelformat).buf);
}
E_LOG_INFO(buf_list, "Using: %ux%u/%s",
fmt->fmt.pix.width, fmt->fmt.pix.height, fourcc_to_string(fmt->fmt.pix.pixelformat).buf);
return 0;
error:
return -1;
}
int buffer_list_request(buffer_list_t *buf_list, int nbufs)
{
struct v4l2_requestbuffers v4l2_req = {0};
v4l2_req.count = nbufs;
v4l2_req.type = buf_list->type;
v4l2_req.memory = buf_list->do_mmap ? V4L2_MEMORY_MMAP : V4L2_MEMORY_DMABUF;
E_LOG_DEBUG(buf_list, "Requesting %u buffers", v4l2_req.count);
E_XIOCTL(buf_list, buf_list->device->fd, VIDIOC_REQBUFS, &v4l2_req, "Can't request buffers");
if (v4l2_req.count < 1) {
E_LOG_ERROR(buf_list, "Insufficient buffer memory: %u", v4l2_req.count);
}
E_LOG_DEBUG(buf_list, "Got %u buffers", v4l2_req.count);
buf_list->bufs = calloc(v4l2_req.count, sizeof(buffer_t*));
buf_list->nbufs = v4l2_req.count;
for (unsigned i = 0; i < buf_list->nbufs; i++) {
char name[64];
sprintf(name, "%s:buf%d", buf_list->name, i);
buffer_t *buf = buffer_open(name, buf_list, i);
if (!buf) {
E_LOG_ERROR(buf_list, "Cannot open buffer: %u", i);
goto error;
}
buf_list->bufs[i] = buf;
}
E_LOG_DEBUG(buf_list, "Opened %u buffers", buf_list->nbufs);
return 0;
error:
return -1;
}
int buffer_list_stream(buffer_list_t *buf_list, bool do_on)
{
enum v4l2_buf_type type = buf_list->type;
E_XIOCTL(buf_list, buf_list->device->fd, do_on ? VIDIOC_STREAMON : VIDIOC_STREAMOFF, &type, "Cannot set streaming state");
return 0;
error:
return -1;
}

30
buffer_list.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include "v4l2.h"
typedef struct buffer_list_s {
char *name;
struct device_s *device;
buffer_t **bufs;
int nbufs;
int type;
struct v4l2_format v4l2_format;
bool do_mplanes;
bool do_mmap;
bool do_dma;
bool do_capture;
} buffer_list_t;
buffer_list_t *buffer_list_open(const char *name, struct device_s *dev, unsigned type);
void buffer_list_close(buffer_list_t *buf_list);
int buffer_list_set_format(buffer_list_t *buffer_list, unsigned width, unsigned height, unsigned format);
int buffer_list_request(buffer_list_t *buf_list, int nbufs);
bool buffer_list_wait_pool(buffer_list_t *buf_list, int timeout);
buffer_t *buffer_list_output_enqueue(buffer_list_t *buf_list, buffer_t *dma_buf);
buffer_t *buffer_list_capture_dequeue(buffer_list_t *buf_list);
int buffer_list_stream(buffer_list_t *buf_list, bool do_on);

120
buffer_queue.c Normal file
View File

@ -0,0 +1,120 @@
#include "buffer.h"
#include "buffer_list.h"
#include "device.h"
bool buffer_output_dequeue(buffer_t *buf)
{
// if (buf->enqueued || buf->buf_list->do_capture) {
// return false;
// }
return false;
}
bool buffer_capture_enqueue(buffer_t *buf)
{
if (buf->enqueued || !buf->buf_list->do_mmap || !buf->buf_list->do_capture) {
return false;
}
E_LOG_DEBUG(buf, "Queuing buffer...");
E_XIOCTL(buf, buf->buf_list->device->fd, VIDIOC_QBUF, &buf->v4l2_buffer, "Can't queue buffer.");
buf->enqueued = true;
error:
return true;
}
buffer_t *buffer_list_output_enqueue(buffer_list_t *buf_list, buffer_t *dma_buf)
{
// if (dma_buf->enqueued || dma_buf->dma_fd < 0) {
// return NULL;
// }
// struct v4l2_buffer v4l2_buf = {0};
// struct v4l2_plane v4l2_plane = {0};
// v4l2_buf.type = buf_list->type;
// v4l2_buf.memory = V4L2_MEMORY_MMAP;
// if (buf_list->do_mplanes) {
// v4l2_buf.length = 1;
// v4l2_buf.m.planes = &v4l2_plane;
// }
// E_XIOCTL(buf_list, buf_list->device->fd, VIDIOC_DQBUF, &v4l2_buf, "Can't grab capture buffer");
// E_LOG_DEBUG(buf_list, "Grabbed INPUT buffer=%u", v4l2_buf.index);
return NULL;
}
buffer_t *buffer_list_capture_dequeue(buffer_list_t *buf_list)
{
if (!buf_list->do_mmap || !buf_list->do_capture) {
return NULL;
}
struct v4l2_buffer v4l2_buf = {0};
struct v4l2_plane v4l2_plane = {0};
v4l2_buf.type = buf_list->type;
v4l2_buf.memory = V4L2_MEMORY_MMAP;
if (buf_list->do_mplanes) {
v4l2_buf.length = 1;
v4l2_buf.m.planes = &v4l2_plane;
}
E_XIOCTL(buf_list, buf_list->device->fd, VIDIOC_DQBUF, &v4l2_buf, "Can't grab capture buffer");
E_LOG_DEBUG(buf_list, "Grabbed INPUT buffer=%u, bytes=%d",
v4l2_buf.index, v4l2_buf.length);
buffer_t *buf = buf_list->bufs[v4l2_buf.index];
buf->v4l2_plane = v4l2_plane;
buf->v4l2_buffer = v4l2_buf;
if (buf_list->do_mplanes) {
buf->v4l2_buffer.m.planes = &buf->v4l2_plane;
}
buf->enqueued = false;
return buf;
error:
return NULL;
}
bool buffer_list_wait_pool(buffer_list_t *buf_list, int timeout) {
struct pollfd fds = {buf_list->device->fd, POLLIN, 0};
if (poll(&fds, 1, timeout) < 0 && errno != EINTR) {
E_LOG_ERROR(buf_list, "Can't poll encoder");
}
E_LOG_DEBUG(buf_list, "Polling encoder %d, %d...", errno, fds.revents);
if (fds.revents & POLLIN) {
return true;
}
if (fds.revents & POLLPRI) {
E_LOG_DEBUG(buf_list, "fd POLLPRI");
}
if (fds.revents & POLLOUT) {
E_LOG_DEBUG(buf_list, "fd POLLOUT");
}
if (fds.revents & POLLERR) {
E_LOG_DEBUG(buf_list, "fd POLLERR");
device_consume_event(buf_list->device);
}
if (fds.revents & POLLHUP) {
E_LOG_DEBUG(buf_list, "fd POLLHUP");
}
if (fds.revents & POLLNVAL) {
E_LOG_DEBUG(buf_list, "fd POLLNVAL");
}
return false;
error:
return false;
}

BIN
camera_stream Executable file

Binary file not shown.

141
device.c Normal file
View File

@ -0,0 +1,141 @@
#include "device.h"
#include "buffer.h"
#include "buffer_list.h"
device_t *device_open(const char *name, const char *path) {
device_t *dev = calloc(1, sizeof(device_t));
dev->name = strdup(name);
dev->path = strdup(path);
dev->fd = open(path, O_RDWR|O_NONBLOCK);
if(dev->fd < 0) {
E_LOG_ERROR(dev, "Can't open device");
}
E_LOG_DEBUG(dev, "Querying device capabilities ...");
E_XIOCTL(dev, dev->fd, VIDIOC_QUERYCAP, &dev->v4l2_cap, "Can't query device capabilities");\
if (!(dev->v4l2_cap.capabilities & V4L2_CAP_STREAMING)) {
E_LOG_ERROR(dev, "Device doesn't support streaming IO");
}
E_LOG_INFO(dev, "Device path=%s fd=%d opened", dev->path, dev->fd);
return dev;
error:
device_close(dev);
return NULL;
}
void device_close(device_t *dev) {
if(dev == NULL) {
return;
}
if (dev->capture_list) {
buffer_list_close(dev->capture_list);
dev->capture_list = NULL;
}
if (dev->output_list) {
buffer_list_close(dev->output_list);
dev->output_list = NULL;
}
if(dev->fd >= 0) {
close(dev->fd);
}
free(dev);
}
int device_open_buffer_list(device_t *dev, bool do_capture, unsigned width, unsigned height, unsigned format, int nbufs)
{
unsigned type;
char name[64];
struct buffer_list_s **buf_list = NULL;
if (do_capture) {
buf_list = &dev->capture_list;
if (dev->v4l2_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
sprintf(name, "%s:capture", dev->name);
} else if (dev->v4l2_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) {
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
sprintf(name, "%s:capture:mplane", dev->name);
} else {
E_LOG_ERROR(dev, "Video capture is not supported by device");
}
} else {
buf_list = &dev->capture_list;
if (dev->v4l2_cap.capabilities & V4L2_CAP_VIDEO_OUTPUT) {
type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
sprintf(name, "%s:output", dev->name);
} else if (dev->v4l2_cap.capabilities & V4L2_CAP_VIDEO_OUTPUT_MPLANE) {
type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
sprintf(name, "%s:output:mplane", dev->name);
} else {
E_LOG_ERROR(dev, "Video output is not supported by device");
}
}
*buf_list = buffer_list_open(name, dev, type);
if (!*buf_list) {
goto error;
}
if (buffer_list_set_format(*buf_list, width, height, format) < 0) {
goto error;
}
if (buffer_list_request(*buf_list, nbufs) < 0) {
goto error;
}
return 0;
error:
buffer_list_close(*buf_list);
*buf_list = NULL;
return -1;
}
int device_stream(device_t *dev, bool do_on)
{
if (dev->capture_list) {
if (buffer_list_stream(dev->capture_list, do_on) < 0) {
return -1;
}
}
if (dev->output_list) {
if (buffer_list_stream(dev->output_list, do_on) < 0) {
return -1;
}
}
return 0;
}
int device_consume_event(device_t *dev)
{
struct v4l2_event event;
E_LOG_DEBUG(dev, "Consuming V4L2 event ...");
E_XIOCTL(dev, dev->fd, VIDIOC_DQEVENT, &event, "Got some V4L2 device event, but where is it?");
switch (event.type) {
case V4L2_EVENT_SOURCE_CHANGE:
E_LOG_INFO(dev, "Got V4L2_EVENT_SOURCE_CHANGE: source changed");
return -1;
case V4L2_EVENT_EOS:
E_LOG_INFO(dev, "Got V4L2_EVENT_EOS: end of stream (ignored)");
return 0;
}
return 0;
error:
return -1;
}

21
device.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include "v4l2.h"
typedef struct device_s {
char *name;
char *path;
int fd;
struct v4l2_capability v4l2_cap;
struct buffer_list_s *capture_list;
struct buffer_list_s *output_list;
} device_t;
device_t *device_open(const char *name, const char *path);
void device_close(device_t *device);
int device_open_buffer_list(device_t *dev, bool do_capture, unsigned width, unsigned height, unsigned format, int nbufs);
int device_consume_event(device_t *device);
int device_stream(device_t *dev, bool do_on);

82
main.c Normal file
View File

@ -0,0 +1,82 @@
#include "buffer.h"
#include "buffer_list.h"
#include "device.h"
#include "v4l2.h"
int camera_width = 1920;
int camera_height = 1080;
int camera_format = V4L2_PIX_FMT_SRGGB10P;
int camera_nbufs = 4;
device_t *camera = NULL;
device_t *isp_srgb = NULL;
device_t *isp_yuuv = NULL;
device_t *isp_yuuv_low = NULL;
int open_camera(const char *path)
{
camera = device_open("CAMERA", path);
if (!camera) {
return -1;
}
if (device_open_buffer_list(camera, true, camera_width, camera_height, camera_format, camera_nbufs) < 0) {
return -1;
}
return 0;
}
int open_isp(const char *srgb_path, const char *yuuv_path, const char *yuuv_low_path)
{
isp_srgb = device_open("ISP-SRGB", srgb_path);
isp_yuuv = device_open("ISP-YUUV", yuuv_path);
isp_yuuv_low = device_open("ISP-YUUV-LOW", yuuv_low_path);
if (!isp_srgb || !isp_yuuv || !isp_yuuv_low) {
return -1;
}
if (device_open_buffer_list(isp_srgb, false, camera_width, camera_height, camera_format, camera_nbufs) < 0 ||
device_open_buffer_list(isp_yuuv, true, camera_width, camera_height, V4L2_PIX_FMT_YUYV, camera_nbufs) < 0 ||
device_open_buffer_list(isp_yuuv, true, camera_width / 2, camera_height / 2, V4L2_PIX_FMT_YUYV, camera_nbufs) < 0) {
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
if (open_camera("/dev/video0") < 0) {
goto error;
}
// if (open_isp("/dev/video13", "/dev/video14", "/dev/video15") < 0) {
// goto error;
// }
// return;
if (device_stream(camera, true) < 0) {
goto error;
}
//return;
while(true) {
if (buffer_list_wait_pool(camera->capture_list, 1000000)) {
buffer_t *buf;
if (buf = buffer_list_capture_dequeue(camera->capture_list)) {
E_LOG_INFO(camera, "Got camera buffer: %p", buf);
buffer_capture_enqueue(buf);
}
}
}
error:
device_close(isp_yuuv_low);
device_close(isp_yuuv);
device_close(isp_srgb);
device_close(camera);
return 0;
}

69
v4l2.c Normal file
View File

@ -0,0 +1,69 @@
#include "v4l2.h"
int xioctl(int fd, 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
)
);
// cppcheck-suppress knownConditionTrueFalse
if (retval && retries <= 0) {
E_LOG_PERROR(NULL, "ioctl(%d) retried %u times; giving up", request, XIOCTL_RETRIES);
}
return retval;
}
fourcc_string fourcc_to_string(unsigned format)
{
fourcc_string fourcc;
char *ptr = fourcc.buf;
*ptr++ = format & 0x7F;
*ptr++ = (format >> 8) & 0x7F;
*ptr++ = (format >> 16) & 0x7F;
*ptr++ = (format >> 24) & 0x7F;
if (format & ((unsigned)1 << 31)) {
*ptr++ = '-';
*ptr++ = 'B';
*ptr++ = 'E';
*ptr++ = '\0';
} else {
*ptr++ = '\0';
}
*ptr++ = 0;
return fourcc;
}
static size_t align_size(size_t size, size_t to)
{
return ((size + (to - 1)) & ~(to - 1));
}
unsigned fourcc_to_stride(unsigned width, unsigned format)
{
switch (format) {
case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_UYVY:
case V4L2_PIX_FMT_RGB565:
return align_size(width * 2, 32);
case V4L2_PIX_FMT_RGB24:
return align_size(width * 3, 32);
case V4L2_PIX_FMT_SRGGB10P:
return align_size(width * 5 / 4, 32);
default:
E_LOG_PERROR(NULL, "Unknown format: %s", fourcc_to_string(format));
}
}

46
v4l2.h Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#ifndef CFG_XIOCTL_RETRIES
# define CFG_XIOCTL_RETRIES 4
#endif
#define XIOCTL_RETRIES ((unsigned)(CFG_XIOCTL_RETRIES))
// assumes that name is first item
#define dev_name(dev) (dev ? *(const char**)dev : "?")
#define E_LOG_ERROR(dev, _msg, ...) do { fprintf(stderr, "%s: " _msg "\n", dev_name(dev), ##__VA_ARGS__); goto error; } while(0)
#define E_LOG_PERROR(dev, _msg, ...) do { fprintf(stderr, "%s: " _msg "\n", dev_name(dev), ##__VA_ARGS__); exit(-1); } while(0)
#define E_LOG_INFO(dev, _msg, ...) fprintf(stderr, "%s: " _msg "\n", dev_name(dev), ##__VA_ARGS__)
#define E_LOG_VERBOSE(dev, _msg, ...) fprintf(stderr, "%s: " _msg "\n", dev_name(dev), ##__VA_ARGS__)
#define E_LOG_DEBUG(dev, _msg, ...) fprintf(stderr, "%s: " _msg "\n", dev_name(dev), ##__VA_ARGS__)
typedef struct {
char buf[10];
} fourcc_string;
fourcc_string fourcc_to_string(unsigned format);
unsigned fourcc_to_stride(unsigned width, unsigned format);
int xioctl(int fd, int request, void *arg);
#define E_XIOCTL(dev, _fd, _request, _value, _msg, ...) do { \
int ret; \
if ((ret = xioctl(_fd, _request, _value)) < 0) { \
E_LOG_ERROR(dev, "xioctl(ret=%d): " _msg, ret, ##__VA_ARGS__); \
} \
} while(0)