Organize sources

This commit is contained in:
Kamil Trzcinski
2022-04-05 08:33:27 +02:00
parent 1aeceb3637
commit 2562d9881b
25 changed files with 60 additions and 57 deletions

78
hw/buffer.c Normal file
View File

@ -0,0 +1,78 @@
#include "hw/buffer.h"
#include "hw/buffer_list.h"
#include "hw/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;
buf->mmap_reflinks = 1;
buf->used = 0;
buf->v4l2_buffer.type = buf_list->type;
buf->v4l2_buffer.index = index;
if (buf_list->do_mplanes) {
buf->v4l2_buffer.length = 1;
buf->v4l2_buffer.m.planes = &buf->v4l2_plane;
buf->v4l2_plane.data_offset = 0;
}
if (buf_list->do_mmap) {
buf->v4l2_buffer.memory = V4L2_MEMORY_MMAP;
} else {
buf->v4l2_buffer.memory = V4L2_MEMORY_DMABUF;
}
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;
}
if (buf_list->do_mmap) {
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;
}
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);
}

26
hw/buffer.h Normal file
View File

@ -0,0 +1,26 @@
#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 used;
size_t length;
struct v4l2_buffer v4l2_buffer;
struct v4l2_plane v4l2_plane;
int dma_fd;
int mmap_reflinks;
struct buffer_s *mmap_source;
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_use(buffer_t *buf);
bool buffer_consumed(buffer_t *buf);

217
hw/buffer_list.c Normal file
View File

@ -0,0 +1,217 @@
#include "hw/buffer.h"
#include "hw/buffer_list.h"
#include "hw/device.h"
buffer_list_t *buffer_list_open(const char *name, struct device_s *dev, unsigned type, bool do_mmap)
{
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:
buf_list->do_mmap = do_mmap;
break;
case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
buf_list->do_mmap = do_mmap;
buf_list->do_mplanes = true;
break;
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
buf_list->do_dma = do_mmap;
buf_list->do_mmap = do_mmap;
buf_list->do_capture = true;
break;
case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
buf_list->do_dma = do_mmap;
buf_list->do_mmap = do_mmap;
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;
bool retried = false;
unsigned orig_width = width;
unsigned orig_height = height;
unsigned stride = 0;
retry:
if (strstr(buf_list->name, "JPEG")) {
width = (width) / 16 * 16;
height = (height) / 16 * 16;
stride = orig_width * 2;
printf("JPEG: %dx%d vs %dx%d\n", orig_width, orig_height, width, height);
}
if (buf_list->do_mplanes) {
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 = stride;// fourcc_to_stride(orig_width, format);
} else {
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);
}
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_INFO(buf_list, "Requested resolution=%ux%u is unavailable. Got %ux%u.",
width, height, fmt->fmt.pix.width, fmt->fmt.pix.height);
if (retried) {
E_LOG_ERROR(buf_list, "Did retry.");
}
retried = true;
goto retry;
}
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);
buf_list->fmt_width = fmt->fmt.pix.width;
buf_list->fmt_height = fmt->fmt.pix.height;
buf_list->fmt_format = fmt->fmt.pix.pixelformat;
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)
{
if (!buf_list) {
return -1;
}
if (buf_list->streaming == do_on) {
return 0;
}
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");
buf_list->streaming = do_on;
for (int i = 0; i < buf_list->nbufs; i++) {
buffer_t *buf = buf_list->bufs[i];
// dequeue buffers (when stream off)
// if (buf->enqueued) {
// if (buf->mmap_source) {
// buf->mmap_source->used = 0;
// buffer_consumed(buf->mmap_source);
// buf->mmap_source = NULL;
// }
// buf->enqueued = false;
// buf->mmap_reflinks = 1;
// }
// re-enqueue buffers on stream start
if (buf_list->streaming && buf_list->do_capture && !buf->enqueued && buf->mmap_reflinks == 1) {
if (buf_list->do_mmap) {
buffer_consumed(buf);
}
}
}
E_LOG_DEBUG(buf_list, "Streaming %s...", do_on ? "started" : "stopped");
return 0;
error:
return -1;
}

36
hw/buffer_list.h Normal file
View File

@ -0,0 +1,36 @@
#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;
unsigned fmt_width, fmt_height, fmt_format;
bool streaming;
int frames;
} buffer_list_t;
buffer_list_t *buffer_list_open(const char *name, struct device_s *dev, unsigned type, bool do_mmap);
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);
int buffer_list_stream(buffer_list_t *buf_list, bool do_on);
buffer_t *buffer_list_find_slot(buffer_list_t *buf_list);
buffer_t *buffer_list_dequeue(buffer_list_t *buf_list);
int buffer_list_count_enqueued(buffer_list_t *buf_list);
int buffer_list_enqueue(buffer_list_t *buf_list, buffer_t *dma_buf);

80
hw/buffer_lock.c Normal file
View File

@ -0,0 +1,80 @@
#include "hw/buffer_lock.h"
bool buffer_lock_is_used(buffer_lock_t *buf_lock)
{
int refs = 0;
pthread_mutex_lock(&buf_lock->lock);
refs = buf_lock->refs;
pthread_mutex_unlock(&buf_lock->lock);
return refs;
}
void buffer_lock_use(buffer_lock_t *buf_lock, int ref)
{
pthread_mutex_lock(&buf_lock->lock);
buf_lock->refs += ref;
pthread_mutex_unlock(&buf_lock->lock);
}
bool buffer_lock_needs_buffer(buffer_lock_t *buf_lock)
{
struct timeval now;
gettimeofday(&now, NULL);
bool needs_buffer = false;
pthread_mutex_lock(&buf_lock->lock);
if (now.tv_sec - buf_lock->buf_time.tv_sec > 1) {
buffer_consumed(buf_lock->buf);
buf_lock->buf = NULL;
needs_buffer = true;
}
if (buf_lock->refs > 0) {
needs_buffer = true;
}
pthread_mutex_unlock(&buf_lock->lock);
return needs_buffer;
}
void buffer_lock_capture(buffer_lock_t *buf_lock, buffer_t *buf)
{
pthread_mutex_lock(&buf_lock->lock);
buffer_consumed(buf_lock->buf);
buffer_use(buf);
buf_lock->buf = buf;
buf_lock->counter++;
gettimeofday(&buf_lock->buf_time, NULL);
E_LOG_DEBUG(buf_lock, "Captured HTTP snapshot: %d", buf_lock->counter);
pthread_cond_broadcast(&buf_lock->cond_wait);
pthread_mutex_unlock(&buf_lock->lock);
}
buffer_t *buffer_lock_get(buffer_lock_t *buf_lock, int timeout_s, int *counter)
{
buffer_t *buf = NULL;
struct timeval now;
struct timespec timeout;
gettimeofday(&now, NULL);
timeout.tv_nsec = now.tv_usec;
timeout.tv_sec = now.tv_sec + timeout_s;
pthread_mutex_lock(&buf_lock->lock);
if (*counter == buf_lock->counter || !buf_lock->buf) {
int ret = pthread_cond_timedwait(&buf_lock->cond_wait, &buf_lock->lock, &timeout);
if (ret == ETIMEDOUT) {
goto ret;
} else if (ret < 0) {
goto ret;
}
}
buf = buf_lock->buf;
buffer_use(buf);
*counter = buf_lock->counter;
ret:
pthread_mutex_unlock(&buf_lock->lock);
return buf;
}

23
hw/buffer_lock.h Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include "buffer.h"
#include <pthread.h>
typedef struct buffer_lock_s {
const char *name;
pthread_mutex_t lock;
pthread_cond_t cond_wait;
buffer_t *buf;
struct timeval buf_time;
int counter;
int refs;
} buffer_lock_t;
#define DEFINE_BUFFER_LOCK(name) static buffer_lock_t name = { #name, PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, NULL, 0 };
void buffer_lock_capture(buffer_lock_t *buf_lock, buffer_t *buf);
buffer_t *buffer_lock_get(buffer_lock_t *buf_lock, int timeout_s, int *counter);
bool buffer_lock_needs_buffer(buffer_lock_t *buf_lock);
void buffer_lock_use(buffer_lock_t *buf_lock, int ref);
bool buffer_lock_is_used(buffer_lock_t *buf_lock);

197
hw/buffer_queue.c Normal file
View File

@ -0,0 +1,197 @@
#include "hw/buffer.h"
#include "hw/buffer_list.h"
#include "hw/device.h"
#include <pthread.h>
pthread_mutex_t buffer_lock = PTHREAD_MUTEX_INITIALIZER;
bool buffer_use(buffer_t *buf)
{
if (!buf) {
return false;
}
pthread_mutex_lock(&buffer_lock);
if (!buf->enqueued) {
pthread_mutex_unlock(&buffer_lock);
return false;
}
buf->mmap_reflinks += 1;
pthread_mutex_unlock(&buffer_lock);
return true;
}
bool buffer_consumed(buffer_t *buf)
{
if (!buf) {
return false;
}
pthread_mutex_lock(&buffer_lock);
if (buf->mmap_reflinks > 0) {
buf->mmap_reflinks--;
}
if (!buf->enqueued && buf->mmap_reflinks == 0) {
// update used bytes
if (buf->buf_list->do_mplanes) {
buf->v4l2_plane.bytesused = buf->used;
buf->v4l2_plane.length = buf->length;
buf->v4l2_plane.data_offset = 0;
} else {
buf->v4l2_buffer.bytesused = buf->used;
}
#if 0
uint64_t now = get_now_monotonic_u64();
struct timeval ts = {
.tv_sec = now / 1000000,
.tv_usec = now % 1000000,
};
buf->v4l2_buffer.timestamp.tv_sec = ts.tv_sec;
buf->v4l2_buffer.timestamp.tv_usec = ts.tv_usec;
#endif
E_LOG_DEBUG(buf, "Queuing buffer... used=%zu length=%zu (linked=%s)", buf->used, buf->length, buf->mmap_source ? buf->mmap_source->name : NULL);
E_XIOCTL(buf, buf->buf_list->device->fd, VIDIOC_QBUF, &buf->v4l2_buffer, "Can't queue buffer.");
buf->enqueued = true;
}
pthread_mutex_unlock(&buffer_lock);
return true;
error:
{
buffer_t *mmap_source = buf->mmap_source;
buf->mmap_source = NULL;
pthread_mutex_unlock(&buffer_lock);
if (mmap_source) {
buffer_consumed(mmap_source);
}
}
return false;
}
buffer_t *buffer_list_find_slot(buffer_list_t *buf_list)
{
buffer_t *buf = NULL;
for (int i = 0; i < buf_list->nbufs; i++) {
if (!buf_list->bufs[i]->enqueued) {
buf = buf_list->bufs[i];
break;
}
}
return buf;
}
int buffer_list_count_enqueued(buffer_list_t *buf_list)
{
int n = 0;
for (int i = 0; i < buf_list->nbufs; i++) {
if (buf_list->bufs[i]->enqueued) {
n++;
}
}
return n;
}
int buffer_list_enqueue(buffer_list_t *buf_list, buffer_t *dma_buf)
{
if (!buf_list->do_mmap && !dma_buf->buf_list->do_mmap) {
E_LOG_PERROR(buf_list, "Cannot enqueue non-mmap to non-mmap: %s.", dma_buf->name);
}
buffer_t *buf = buffer_list_find_slot(buf_list);
if (!buf) {
return 0;
}
if (buf_list->do_mmap) {
if (dma_buf->used > buf->length) {
E_LOG_PERROR(buf_list, "The dma_buf (%s) is too long: %zu vs space=%zu",
dma_buf->name, dma_buf->used, buf->length);
}
struct timespec before, after;
clock_gettime(CLOCK_MONOTONIC, &before);
memcpy(buf->start, dma_buf->start, dma_buf->used);
clock_gettime(CLOCK_MONOTONIC, &after);
uint64_t time_diff = after.tv_sec * 1000000LL + after.tv_nsec / 1000 - before.tv_sec * 1000000LL - before.tv_nsec / 1000;
E_LOG_DEBUG(buf, "mmap copy: dest=%p, src=%p (%s), size=%zu, space=%zu, time=%dllus",
buf->start, dma_buf->start, dma_buf->name, dma_buf->used, buf->length, time_diff);
} else {
if (buf_list->do_mplanes) {
buf->v4l2_plane.m.fd = dma_buf->dma_fd;
} else {
buf->v4l2_buffer.m.fd = dma_buf->dma_fd;
}
E_LOG_DEBUG(buf, "dmabuf copy: dest=%p, src=%p (%s, dma_fd=%d), size=%zu",
buf->start, dma_buf->start, dma_buf->name, dma_buf->dma_fd, dma_buf->used);
buf->mmap_source = dma_buf;
buf->length = dma_buf->length;
dma_buf->mmap_reflinks++;
}
buf->used = dma_buf->used;
buffer_consumed(buf);
return 1;
error:
return -1;
}
buffer_t *buffer_list_dequeue(buffer_list_t *buf_list)
{
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");
buffer_t *buf = buf_list->bufs[v4l2_buf.index];
if (buf_list->do_mplanes) {
buf->used = v4l2_plane.bytesused;
} else {
buf->used = v4l2_buf.bytesused;
}
buf->v4l2_buffer.flags = v4l2_buf.flags;
buf->enqueued = false;
buf->mmap_reflinks = 1;
E_LOG_DEBUG(buf_list, "Grabbed mmap buffer=%u, bytes=%d, used=%d, frame=%d, linked=%s",
buf->index, buf->length, buf->used, buf_list->frames, buf->mmap_source ? buf->mmap_source->name : NULL);
if (buf->mmap_source) {
buf->mmap_source->used = 0;
buffer_consumed(buf->mmap_source);
buf->mmap_source = NULL;
}
buf_list->frames++;
return buf;
error:
return NULL;
}

190
hw/device.c Normal file
View File

@ -0,0 +1,190 @@
#include "hw/device.h"
#include "hw/buffer.h"
#include "hw/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);
dev->allow_dma = true;
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;
bool do_mmap = false;
if (do_capture) {
buf_list = &dev->capture_list;
do_mmap = true;
if (dev->capture_list) {
E_LOG_ERROR(dev, "The capture_list is already created.");
}
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 | V4L2_CAP_VIDEO_M2M_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: %08x", dev->v4l2_cap.capabilities);
}
} else {
buf_list = &dev->output_list;
do_mmap = !dev->allow_dma;
if (dev->output_list) {
E_LOG_ERROR(dev, "The output_list is already created.");
}
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 | V4L2_CAP_VIDEO_M2M_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: %08x", dev->v4l2_cap.capabilities);
}
}
*buf_list = buffer_list_open(name, dev, type, do_mmap);
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;
}
int device_force_key(device_t *dev)
{
struct v4l2_control ctl = {0};
ctl.id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME;
ctl.value = 1;
E_LOG_DEBUG(dev, "Forcing keyframe ...");
E_XIOCTL(dev, dev->fd, VIDIOC_S_CTRL, &ctl, "Can't force keyframe");
return 0;
error:
return -1;
}
int device_set_fps(device_t *dev, int desired_fps)
{
struct v4l2_streamparm setfps = {0};
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
setfps.parm.output.timeperframe.numerator = 1;
setfps.parm.output.timeperframe.denominator = desired_fps;
E_LOG_DEBUG(dev, "Configuring FPS ...");
E_XIOCTL(dev, dev->fd, VIDIOC_S_PARM, &setfps, "Can't set FPS");
return 0;
error:
return -1;
}
int device_set_option(device_t *dev, const char *name, uint32_t id, int32_t value)
{
struct v4l2_control ctl = {0};
ctl.id = id;
ctl.value = value;
E_LOG_DEBUG(dev, "Configuring option %s (%08x) = %d", name, id, value);
E_XIOCTL(dev, dev->fd, VIDIOC_S_CTRL, &ctl, "Can't set option %s", name);
return 0;
error:
return -1;
}

36
hw/device.h Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include "v4l2.h"
typedef struct device_s {
char *name;
char *path;
int fd;
struct v4l2_capability v4l2_cap;
bool allow_dma;
struct buffer_list_s *capture_list;
struct buffer_list_s *output_list;
struct device_s *output_device;
} 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);
int device_force_key(device_t *dev);
int device_set_option(device_t *dev, const char *name, uint32_t id, int32_t value);
int device_set_fps(device_t *dev, int desired_fps);
#define DEVICE_SET_OPTION(dev, name, value) \
device_set_option(dev, #name, V4L2_CID_##name, value)
#define DEVICE_SET_OPTION2(dev, type, name, value) \
device_set_option(dev, #name, V4L2_CID_##type##_##name, value)
#define DEVICE_SET_OPTION2_FATAL(dev, type, name, value) \
do { if (DEVICE_SET_OPTION2(dev, type, name, value) < 0) return -1; } while(0)

228
hw/links.c Normal file
View File

@ -0,0 +1,228 @@
#include "hw/device.h"
#include "hw/buffer.h"
#include "hw/buffer_list.h"
#include "hw/links.h"
#define N_FDS 50
int _build_fds(link_t *all_links, struct pollfd *fds, link_t **links, buffer_list_t **buf_lists, int max_n)
{
int n = 0;
for (int i = 0; all_links[i].capture; i++) {
link_t *link = &all_links[i];
if (!link->capture || !link->capture->capture_list || n >= max_n) {
return -EINVAL;
}
if (!link->capture->capture_list->do_mmap) {
continue;
}
if (!link->capture->capture_list->streaming) {
continue;
}
bool can_consume = true;
for (int j = 0; link->outputs[j]; j++) {
device_t *output = link->outputs[j];
if (!output || !output->output_list || n >= max_n) {
return -EINVAL;
}
if (!output->output_list->streaming) {
continue;
}
int count_enqueued = buffer_list_count_enqueued(output->output_list);
if (count_enqueued == output->output_list->nbufs) {
E_LOG_DEBUG(link->capture->capture_list, "Cannot consume due to %s using %d of %d",
output->output_list->name, count_enqueued, output->output_list->nbufs);
can_consume = false;
}
// Can something be dequeued?
if (count_enqueued == 0) {
continue;
}
struct pollfd fd = {output->fd, POLLOUT};
fds[n] = fd;
buf_lists[n] = output->output_list;
links[n] = link;
n++;
}
if (can_consume) {
struct pollfd fd = {link->capture->fd, POLLIN};
fds[n] = fd;
buf_lists[n] = link->capture->capture_list;
links[n] = link;
n++;
}
}
return n;
}
int links_step(link_t *all_links, int timeout)
{
struct pollfd fds[N_FDS] = {0};
link_t *links[N_FDS];
buffer_list_t *buf_lists[N_FDS];
buffer_t *buf;
int n = _build_fds(all_links, fds, links, buf_lists, N_FDS);
int ret = poll(fds, n, timeout);
if (ret < 0 && errno != EINTR) {
return errno;
}
printf("links_step n=%d, ret=%d\n", n, ret);
for (int i = 0; i < n; i++) {
buffer_list_t *buf_list = buf_lists[i];
link_t *link = links[i];
E_LOG_DEBUG(buf_list, "pool i=%d revents=%08x streaming=%d enqueued=%d/%d", i, fds[i].revents, buf_list->streaming,
buffer_list_count_enqueued(buf_list), buf_list->nbufs);
if (fds[i].revents & POLLIN) {
E_LOG_DEBUG(buf_list, "POLLIN");
if (buf = buffer_list_dequeue(buf_list)) {
for (int j = 0; link->outputs[j]; j++) {
buffer_list_enqueue(link->outputs[j]->output_list, buf);
}
if (link->callbacks.on_buffer) {
link->callbacks.on_buffer(buf);
}
buffer_consumed(buf);
}
}
if (fds[i].revents & POLLOUT) {
E_LOG_DEBUG(buf_list, "POLLOUT");
buffer_list_dequeue(buf_list);
}
if (fds[i].revents & POLLERR) {
E_LOG_DEBUG(buf_list, "POLLERR");
device_consume_event(buf_list->device);
}
if (fds[i].revents & ~(POLLIN|POLLOUT|POLLERR)) {
E_LOG_DEBUG(buf_list, "POLL%08x", fds[i].revents);
}
}
return 0;
}
int links_stream(link_t *all_links, bool do_stream)
{
int n;
for (n = 0; all_links[n].capture; n++);
for (int i = n; --i >= 0; ) {
bool streaming = true;
link_t *link = &all_links[i];
if (link->callbacks.check_streaming) {
streaming = link->callbacks.check_streaming();
}
if (buffer_list_stream(link->capture->capture_list, streaming) < 0) {
E_LOG_ERROR(link->capture, "Failed to start streaming");
}
for (int j = 0; link->outputs[j]; j++) {
if (buffer_list_stream(link->outputs[j]->output_list, streaming) < 0) {
E_LOG_ERROR(link->outputs[j], "Failed to start streaming");
}
}
}
return 0;
error:
return -1;
}
// int links_open_buffer_list_from(device_t *dev, bool do_capture, buffer_list_t *parent_buffer, unsigned format)
// {
// if (!parent_buffer) {
// return -1;
// }
// return device_open_buffer_list(
// dev,
// do_capture,
// parent_buffer->fmt_width,
// parent_buffer->fmt_height,
// format ? format : parent_buffer->fmt_format,
// parent_buffer->nbufs
// );
// }
// int links_init(link_t *all_links)
// {
// // create all outputs (sinks)
// for (int i = 0; all_links[i].capture; i++) {
// link_t *link = &all_links[i];
// if (!link->capture) {
// E_LOG_ERROR(NULL, "Missing link capture.");
// }
// if (link->capture_format) {
// int ret = links_open_buffer_list_from(
// link->capture,
// true,
// link->capture->upstream_device ? link->capture->upstream_device->output_list : link->capture->output_list,
// link->capture_format
// );
// if (ret < 0) {
// E_LOG_ERROR(link->capture, "Failed to create capture_list.");
// }
// }
// if (!link->capture->capture_list) {
// E_LOG_ERROR(link->capture, "Missing capture device.");
// }
// for (int j = 0; link->outputs[j]; j++) {
// device_t *output = link->outputs[j];
// int ret = links_open_buffer_list_from(output, false, link->capture->capture_list, 0);
// if (ret < 0) {
// E_LOG_ERROR(output, "Failed to create output_list.");
// }
// }
// }
// return 0;
// error:
// return -1;
// }
int links_loop(link_t *all_links, bool *running)
{
*running = true;
if (links_stream(all_links, true) < 0) {
return -1;
}
while(*running) {
if (links_step(all_links, 1000) < 0) {
links_stream(all_links, false);
return -1;
}
//usleep(100*1000);
}
links_stream(all_links, false);
return 0;
}

19
hw/links.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include "v4l2.h"
typedef void (*link_on_buffer)(struct buffer_s *buf);
typedef bool (*link_check_streaming)();
typedef struct link_s {
struct device_s *capture; // capture_list
struct device_s *outputs[10];
struct {
link_on_buffer on_buffer;
link_check_streaming check_streaming;
} callbacks;
} link_t;
int links_init(link_t *all_links);
int links_step(link_t *all_links, int timeout);
int links_loop(link_t *all_links, bool *running);

76
hw/v4l2.c Normal file
View File

@ -0,0 +1,76 @@
#include "hw/v4l2.h"
int xioctl(const char *name, 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, "%s: ioctl(%08x) retried %u times; giving up", name, 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_YUV420:
return align_size(width * 3 / 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);
case V4L2_PIX_FMT_JPEG:
case V4L2_PIX_FMT_H264:
return 0;
default:
E_LOG_PERROR(NULL, "Unknown format: %s", fourcc_to_string(format).buf);
}
}

47
hw/v4l2.h Normal file
View File

@ -0,0 +1,47 @@
#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 <pthread.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(const char *name, int fd, int request, void *arg);
#define E_XIOCTL(dev, _fd, _request, _value, _msg, ...) do { \
int ret; \
if ((ret = xioctl(dev_name(dev), _fd, _request, _value)) < 0) { \
E_LOG_ERROR(dev, "xioctl(ret=%d): " _msg, ret, ##__VA_ARGS__); \
} \
} while(0)