Move all into device/

This commit is contained in:
Kamil Trzcinski
2022-04-08 18:47:12 +02:00
parent 45724981ad
commit 42ab32b1a6
28 changed files with 75 additions and 73 deletions

137
device/camera.c Normal file
View File

@ -0,0 +1,137 @@
#include "camera.h"
#include "device/hw/device.h"
#include "device/hw/buffer_list.h"
#include "device/hw/links.h"
#include "device/hw/v4l2.h"
void camera_init(camera_t *camera)
{
memset(camera, 0, sizeof(*camera));
camera->name = "CAMERA";
}
void camera_close(camera_t *camera)
{
if (!camera)
return;
for (int i = MAX_DEVICES; i-- > 0; ) {
if (camera->devices[i]) {
device_close(camera->devices[i]);
camera->devices[i] = NULL;
}
}
for (int i = MAX_DEVICES; i-- > 0; ) {
if (camera->links[i].callbacks.on_buffer) {
camera->links[i].callbacks.on_buffer = NULL;
}
}
memset(camera->links, 0, sizeof(camera->links));
free(camera);
}
camera_t *camera_open(camera_options_t *options)
{
camera_t *camera = calloc(1, sizeof(camera_t));
camera->name = "CAMERA";
camera->options = *options;
camera->camera = device_open(camera->name, camera->options.path);
if (!camera->camera) {
goto error;
}
camera->camera->allow_dma = camera->options.allow_dma;
if (strstr(camera->camera->v4l2_cap.bus_info, "usb")) {
E_LOG_INFO(camera, "Disabling DMA since device uses USB (which is likely not working properly).");
camera->camera->allow_dma = false;
}
device_set_pad_format(camera->camera, camera->options.width, camera->options.height, 0);
if (device_open_buffer_list(camera->camera, true, camera->options.width, camera->options.height, camera->options.format, 0, camera->options.nbufs, true) < 0) {
goto error;
}
camera->camera->capture_list->do_timestamps = true;
if (camera->options.fps > 0) {
camera->camera->capture_list->fmt_interval_us = 1000 * 1000 / camera->options.fps;
}
switch (camera->camera->capture_list->fmt_format) {
case V4L2_PIX_FMT_YUYV:
if (camera_configure_direct(camera) < 0) {
goto error;
}
break;
case V4L2_PIX_FMT_MJPEG:
case V4L2_PIX_FMT_H264:
if (camera_configure_decoder(camera) < 0) {
goto error;
}
break;
case V4L2_PIX_FMT_SRGGB10P:
#if 1
if (camera_configure_isp(camera, camera->options.high_res_factor, camera->options.low_res_factor) < 0) {
goto error;
}
#else
if (camera_configure_legacy_isp(camera, camera->options.high_res_factor) < 0) {
goto error;
}
#endif
break;
default:
E_LOG_ERROR(camera, "Unsupported camera format=%s", fourcc_to_string(camera->options.format).buf);
break;
}
if (camera_set_params(camera) < 0) {
goto error;
}
return camera;
error:
camera_close(camera);
return NULL;
}
int camera_set_params(camera_t *camera)
{
device_set_fps(camera->camera, camera->options.fps);
device_set_option_list(camera->camera, camera->options.options);
// DEVICE_SET_OPTION(camera->camera, EXPOSURE, 2684);
// DEVICE_SET_OPTION(camera->camera, ANALOGUE_GAIN, 938);
// DEVICE_SET_OPTION(camera->camera, DIGITAL_GAIN, 512);
// DEVICE_SET_OPTION(camera->camera, VBLANK, 1636);
// DEVICE_SET_OPTION(camera->camera, HBLANK, 6906);
// DEVICE_SET_OPTION(camera->isp_srgb, RED_BALANCE, 2120);
// DEVICE_SET_OPTION(camera->isp_srgb, BLUE_BALANCE, 1472);
// DEVICE_SET_OPTION(camera->isp_srgb, DIGITAL_GAIN, 1007);
DEVICE_SET_OPTION2(camera->codec_jpeg, JPEG, COMPRESSION_QUALITY, 80);
DEVICE_SET_OPTION2(camera->codec_h264, MPEG_VIDEO, BITRATE, 5000 * 1000);
DEVICE_SET_OPTION2(camera->codec_h264, MPEG_VIDEO, H264_I_PERIOD, 30);
DEVICE_SET_OPTION2(camera->codec_h264, MPEG_VIDEO, H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_4_0);
DEVICE_SET_OPTION2(camera->codec_h264, MPEG_VIDEO, REPEAT_SEQ_HEADER, 1);
DEVICE_SET_OPTION2(camera->codec_h264, MPEG_VIDEO, H264_MIN_QP, 16);
DEVICE_SET_OPTION2(camera->codec_h264, MPEG_VIDEO, H264_MIN_QP, 32);
return 0;
}
int camera_run(camera_t *camera)
{
bool running = false;
return links_loop(camera->links, &running);
}

57
device/camera.h Normal file
View File

@ -0,0 +1,57 @@
#pragma once
#include "device/hw/links.h"
#include "device/hw/device.h"
#define MAX_DEVICES 20
#define MAX_HTTP_METHODS 20
#define CAMERA_DEVICE_CAMERA 0
typedef struct camera_options_s {
char path[256];
unsigned width, height, format;
unsigned nbufs, fps;
bool allow_dma;
float high_res_factor;
float low_res_factor;
char options[4096];
} camera_options_t;
typedef struct camera_s {
const char *name;
camera_options_t options;
union {
device_t *devices[MAX_DEVICES];
struct {
device_t *camera;
device_t *decoder; // decode JPEG/H264 into YUVU
device_t *legacy_isp; // convert pRAA/YUVU into YUVU
device_t *isp_srgb;
device_t *isp_yuuv;
device_t *isp_yuuv_lowres;
device_t *codec_jpeg; // encode YUVU into JPEG
device_t *codec_h264; // encode YUVU into H264
device_t *codec_jpeg_lowres; // encode YUVU into JPEG
device_t *codec_h264_lowres; // encode YUVU into H264
};
};
link_t links[MAX_DEVICES];
int nlinks;
} camera_t;
#define CAMERA(DEVICE) camera->devices[DEVICE]
camera_t *camera_open(camera_options_t *camera);
int camera_set_params(camera_t *camera);
void camera_close(camera_t *camera);
int camera_run(camera_t *camera);
int camera_configure_isp(camera_t *camera, float high_div, float low_div);
int camera_configure_legacy_isp(camera_t *camera, float div);
int camera_configure_direct(camera_t *camera);
int camera_configure_decoder(camera_t *camera);

68
device/camera_decoder.c Normal file
View File

@ -0,0 +1,68 @@
#include "camera.h"
#include "device/hw/buffer.h"
#include "device/hw/buffer_list.h"
#include "device/hw/device.h"
#include "device/hw/links.h"
#include "device/hw/v4l2.h"
#include "device/hw/buffer_list.h"
#include "http/http.h"
int camera_configure_decoder(camera_t *camera)
{
buffer_list_t *camera_src = camera->camera->capture_list;
buffer_list_t *src = camera_src;
device_video_force_key(camera->camera);
camera->decoder = device_open("DECODER", "/dev/video10");
if (device_open_buffer_list_output(camera->decoder, src) < 0) {
return -1;
}
if (device_open_buffer_list_capture(camera->decoder, NULL, 1.0, 0, true) < 0) {
return -1;
}
if (device_set_decoder_start(camera->decoder, true) < 0) {
return -1;
}
src = camera->decoder->capture_list;
if (camera_src->fmt_format != V4L2_PIX_FMT_MJPEG && camera_src->fmt_format != V4L2_PIX_FMT_JPEG) {
camera->codec_jpeg = device_open("JPEG", "/dev/video31");
if (device_open_buffer_list_output(camera->codec_jpeg, src) < 0 ||
device_open_buffer_list_capture(camera->codec_jpeg, src, 1.0, V4L2_PIX_FMT_JPEG, true) < 0) {
return -1;
}
}
if (camera_src->fmt_format != V4L2_PIX_FMT_H264) {
camera->codec_h264 = device_open("H264", "/dev/video11");
if (device_open_buffer_list_output(camera->codec_h264, src) < 0 ||
device_open_buffer_list_capture(camera->codec_h264, src, 1.0, V4L2_PIX_FMT_H264, true) < 0) {
return -1;
}
}
link_t *links = camera->links;
if (camera_src->fmt_format == V4L2_PIX_FMT_MJPEG || camera_src->fmt_format == V4L2_PIX_FMT_JPEG) {
*links++ = (link_t){ camera->camera->capture_list, { camera->decoder->output_list }, { http_jpeg_capture, http_jpeg_needs_buffer } };
*links++ = (link_t){ camera->decoder->capture_list, { camera->codec_h264->output_list } };
*links++ = (link_t){ camera->codec_h264->capture_list, { }, { http_h264_capture, http_h264_needs_buffer } };
} else if (camera_src->fmt_format == V4L2_PIX_FMT_H264) {
*links++ = (link_t){ camera->camera->capture_list, { camera->decoder->output_list }, { http_h264_capture, http_h264_needs_buffer }};
*links++ = (link_t){ camera->decoder->capture_list, { camera->codec_jpeg->output_list } };
*links++ = (link_t){ camera->codec_jpeg->capture_list, { }, { http_jpeg_capture, http_jpeg_needs_buffer } };
} else {
*links++ = (link_t){ camera->camera->capture_list, { camera->decoder->output_list } };
*links++ = (link_t){ camera->decoder->capture_list, { camera->codec_jpeg->output_list, camera->codec_h264->output_list } };
*links++ = (link_t){ camera->codec_jpeg->capture_list, { }, { http_jpeg_capture, http_jpeg_needs_buffer } };
*links++ = (link_t){ camera->codec_h264->capture_list, { }, { http_h264_capture, http_h264_needs_buffer } };
}
return 0;
}

34
device/camera_direct.c Normal file
View File

@ -0,0 +1,34 @@
#include "camera.h"
#include "device/hw/buffer.h"
#include "device/hw/buffer_list.h"
#include "device/hw/device.h"
#include "device/hw/links.h"
#include "device/hw/v4l2.h"
#include "device/hw/buffer_list.h"
#include "http/http.h"
int camera_configure_direct(camera_t *camera)
{
buffer_list_t *src = camera->camera->capture_list;
camera->codec_jpeg = device_open("JPEG", "/dev/video31");
camera->codec_h264 = device_open("H264", "/dev/video11");
if (device_open_buffer_list_output(camera->codec_jpeg, src) < 0 ||
device_open_buffer_list_capture(camera->codec_jpeg, src, 1.0, V4L2_PIX_FMT_JPEG, true) < 0) {
return -1;
}
if (device_open_buffer_list_output(camera->codec_h264, src) < 0 ||
device_open_buffer_list_capture(camera->codec_h264, src, 1.0, V4L2_PIX_FMT_H264, true) < 0) {
return -1;
}
link_t *links = camera->links;
*links++ = (link_t){ camera->camera->capture_list, { camera->codec_jpeg->output_list, camera->codec_h264->output_list } };
*links++ = (link_t){ camera->codec_jpeg->capture_list, { }, { http_jpeg_capture, http_jpeg_needs_buffer } };
*links++ = (link_t){ camera->codec_h264->capture_list, { }, { http_h264_capture, http_h264_needs_buffer } };
return 0;
}

76
device/camera_isp.c Normal file
View File

@ -0,0 +1,76 @@
#include "camera.h"
#include "device/hw/buffer.h"
#include "device/hw/buffer_list.h"
#include "device/hw/device.h"
#include "device/hw/links.h"
#include "device/hw/v4l2.h"
#include "device/hw/buffer_list.h"
#include "http/http.h"
void write_yuvu(buffer_t *buffer);
int camera_configure_isp(camera_t *camera, float high_div, float low_div)
{
camera->isp_srgb = device_open("ISP", "/dev/video13");
camera->isp_yuuv = device_open("ISP-YUUV", "/dev/video14");
camera->codec_jpeg = device_open("JPEG", "/dev/video31");
camera->codec_h264 = device_open("H264", "/dev/video11");
if (device_open_buffer_list_output(camera->isp_srgb, camera->camera->capture_list) < 0 ||
device_open_buffer_list_capture(camera->isp_yuuv, camera->camera->capture_list, high_div, V4L2_PIX_FMT_YUYV, true) < 0) {
return -1;
}
camera->isp_yuuv->output_device = camera->isp_srgb;
link_t *links = camera->links;
*links++ = (link_t){ camera->camera->capture_list, { camera->isp_srgb->output_list } };
buffer_list_t *src = camera->isp_yuuv->capture_list;
if (device_open_buffer_list_output(camera->codec_jpeg, src) < 0 ||
device_open_buffer_list_capture(camera->codec_jpeg, src, 1.0, V4L2_PIX_FMT_JPEG, true) < 0) {
return -1;
}
if (device_open_buffer_list_output(camera->codec_h264, src) < 0 ||
device_open_buffer_list_capture(camera->codec_h264, src, 1.0, V4L2_PIX_FMT_H264, true) < 0) {
return -1;
}
*links++ = (link_t){ camera->isp_yuuv->capture_list, { camera->codec_jpeg->output_list, camera->codec_h264->output_list }, { write_yuvu } };
*links++ = (link_t){ camera->codec_jpeg->capture_list, { }, { http_jpeg_capture, http_jpeg_needs_buffer } };
*links++ = (link_t){ camera->codec_h264->capture_list, { }, { http_h264_capture, http_h264_needs_buffer } };
// all done
if (low_div < 1) {
return 0;
}
camera->isp_yuuv_lowres = device_open("ISP-YUUV-LOW", "/dev/video15");
camera->codec_jpeg_lowres = device_open("JPEG-LOW", "/dev/video31");
camera->codec_h264_lowres = device_open("H264-LOW", "/dev/video11");
if (device_open_buffer_list_capture(camera->isp_yuuv_lowres, camera->camera->capture_list, low_div, V4L2_PIX_FMT_YUYV, true) < 0) {
return -1;
}
camera->isp_yuuv_lowres->output_device = camera->isp_srgb;
src = camera->isp_yuuv_lowres->capture_list;
if (device_open_buffer_list_output(camera->codec_jpeg_lowres, src) < 0 ||
device_open_buffer_list_capture(camera->codec_jpeg_lowres, src, 1.0, V4L2_PIX_FMT_JPEG, true) < 0) {
return -1;
}
if (device_open_buffer_list_output(camera->codec_h264_lowres, src) < 0 ||
device_open_buffer_list_capture(camera->codec_h264_lowres, src, 1.0, V4L2_PIX_FMT_H264, true) < 0) {
return -1;
}
*links++ = (link_t){ camera->isp_yuuv_lowres->capture_list, { camera->codec_jpeg_lowres->output_list, camera->codec_h264_lowres->output_list }, { write_yuvu } };
*links++ = (link_t){ camera->codec_jpeg_lowres->capture_list, { }, { http_jpeg_lowres_capture, http_jpeg_needs_buffer } };
*links++ = (link_t){ camera->codec_h264_lowres->capture_list, { }, { http_h264_lowres_capture, http_h264_needs_buffer } };
return 0;
}

View File

@ -0,0 +1,55 @@
#include "camera.h"
#include "device/hw/buffer.h"
#include "device/hw/buffer_list.h"
#include "device/hw/device.h"
#include "device/hw/links.h"
#include "device/hw/v4l2.h"
#include "device/hw/buffer_list.h"
#include "http/http.h"
void write_yuvu(buffer_t *buffer)
{
#if 0
FILE *fp = fopen("/tmp/capture-yuyv.raw.tmp", "wb");
if (fp) {
fwrite(buffer->start, 1, buffer->used, fp);
fclose(fp);
}
rename("/tmp/capture-yuyv.raw.tmp", "/tmp/capture-yuyv.raw");
#endif
}
int camera_configure_legacy_isp(camera_t *camera, float div)
{
buffer_list_t *src = camera->camera->capture_list;
camera->legacy_isp = device_open("ISP", "/dev/video12");
camera->codec_jpeg = device_open("JPEG", "/dev/video31");
camera->codec_h264 = device_open("H264", "/dev/video11");
if (device_open_buffer_list_output(camera->legacy_isp, src) < 0 ||
device_open_buffer_list_capture(camera->legacy_isp, src, div, V4L2_PIX_FMT_YUYV, true) < 0) {
return -1;
}
src = camera->legacy_isp->capture_list;
if (device_open_buffer_list_output(camera->codec_jpeg, src) < 0 ||
device_open_buffer_list_capture(camera->codec_jpeg, src, 1.0, V4L2_PIX_FMT_JPEG, true) < 0) {
return -1;
}
if (device_open_buffer_list_output(camera->codec_h264, src) < 0 ||
device_open_buffer_list_capture(camera->codec_h264, src, 1.0, V4L2_PIX_FMT_H264, true) < 0) {
return -1;
}
link_t *links = camera->links;
*links++ = (link_t){ camera->camera->capture_list, { camera->legacy_isp->output_list } };
*links++ = (link_t){ camera->legacy_isp->capture_list, { camera->codec_jpeg->output_list, camera->codec_h264->output_list }, { write_yuvu, NULL } };
*links++ = (link_t){ camera->codec_jpeg->capture_list, { }, { http_jpeg_capture, http_jpeg_needs_buffer } };
*links++ = (link_t){ camera->codec_h264->capture_list, { }, { http_h264_capture, http_h264_needs_buffer } };
return 0;
}

78
device/hw/buffer.c Normal file
View File

@ -0,0 +1,78 @@
#include "device/hw/buffer.h"
#include "device/hw/buffer_list.h"
#include "device/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);
}

27
device/hw/buffer.h Normal file
View File

@ -0,0 +1,27 @@
#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;
uint64_t enqueue_time_us;
} 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, const char *who);

260
device/hw/buffer_list.c Normal file
View File

@ -0,0 +1,260 @@
#include "device/hw/buffer.h"
#include "device/hw/buffer_list.h"
#include "device/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;
buf_list->do_dma = do_mmap;
break;
case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
buf_list->do_mmap = do_mmap;
buf_list->do_dma = 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, unsigned bytesperline)
{
struct v4l2_format *fmt = &buf_list->v4l2_format;
fmt->type = buf_list->type;
unsigned orig_width = width;
unsigned orig_height = height;
retry:
// JPEG is in 16x16 blocks (shrink image to fit) (but adapt to 32x32)
// And ISP output
if (strstr(buf_list->name, "JPEG") || strstr(buf_list->name, "H264") || buf_list->do_capture && strstr(buf_list->name, "ISP")) {
width = shrink_to_block(width, 32);
height = shrink_to_block(height, 32);
E_LOG_VERBOSE(buf_list, "Adapting size to 32x32 block: %dx%d vs %dx%d", orig_width, orig_height, width, height);
}
if (format == V4L2_PIX_FMT_H264) {
bytesperline = 0;
}
E_LOG_DEBUG(buf_list, "Get current format ...");
E_XIOCTL(buf_list, buf_list->device->fd, VIDIOC_G_FMT, fmt, "Can't set format");
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;
if (format)
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 = bytesperline;
//fmt->fmt.pix_mp.plane_fmt[0].sizeimage = bytesperline * orig_height;
} else {
fmt->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
fmt->fmt.pix.width = width;
fmt->fmt.pix.height = height;
if (format)
fmt->fmt.pix.pixelformat = format;
fmt->fmt.pix.field = V4L2_FIELD_ANY;
fmt->fmt.pix.bytesperline = bytesperline;
//fmt->fmt.pix.sizeimage = bytesperline * orig_height;
}
E_LOG_DEBUG(buf_list, "Configuring format (%s)...", fourcc_to_string(format).buf);
E_XIOCTL(buf_list, buf_list->device->fd, VIDIOC_S_FMT, fmt, "Can't set format");
if (buf_list->do_mplanes) {
buf_list->fmt_width = fmt->fmt.pix_mp.width;
buf_list->fmt_height = fmt->fmt.pix_mp.height;
buf_list->fmt_format = fmt->fmt.pix_mp.pixelformat;
buf_list->fmt_bytesperline = fmt->fmt.pix_mp.plane_fmt[0].bytesperline;
} else {
buf_list->fmt_width = fmt->fmt.pix.width;
buf_list->fmt_height = fmt->fmt.pix.height;
buf_list->fmt_format = fmt->fmt.pix.pixelformat;
buf_list->fmt_bytesperline = fmt->fmt.pix.bytesperline;
}
if (bytesperline > 0 && buf_list->fmt_bytesperline != bytesperline) {
E_LOG_ERROR(buf_list, "Requested bytesperline=%u. Got %u.",
bytesperline, buf_list->fmt_bytesperline);
}
if (buf_list->fmt_width != width || buf_list->fmt_height != height) {
if (bytesperline) {
E_LOG_ERROR(buf_list, "Requested resolution=%ux%u is unavailable. Got %ux%u.",
width, height, buf_list->fmt_width, buf_list->fmt_height);
} else {
E_LOG_INFO(buf_list, "Requested resolution=%ux%u is unavailable. Got %ux%u. Accepted",
width, height, buf_list->fmt_width, buf_list->fmt_height);
}
}
if (format && buf_list->fmt_format != 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(buf_list->fmt_format).buf);
}
E_LOG_INFO(
buf_list,
"Using: %ux%u/%s, bytesperline=%d",
buf_list->fmt_width,
buf_list->fmt_height,
fourcc_to_string(buf_list->fmt_format).buf,
buf_list->fmt_bytesperline
);
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;
int enqueued = buffer_list_count_enqueued(buf_list);
E_LOG_DEBUG(buf_list, "Streaming %s... Was %d of %d enqueud", do_on ? "started" : "stopped", enqueued, buf_list->nbufs);
return 0;
error:
return -1;
}
int buffer_list_refresh_states(buffer_list_t *buf_list)
{
if (!buf_list) {
return -1;
}
struct v4l2_buffer v4l2_buf = {0};
struct v4l2_plane v4l2_plane = {0};
v4l2_buf.type = buf_list->type;
if (buf_list->do_mplanes) {
v4l2_buf.length = 1;
v4l2_buf.m.planes = &v4l2_plane;
}
if (buf_list->do_mmap) {
v4l2_buf.memory = V4L2_MEMORY_MMAP;
} else {
v4l2_buf.memory = V4L2_MEMORY_DMABUF;
}
for (int i = 0; i < buf_list->nbufs; i++) {
v4l2_buf.index = i;
E_XIOCTL(buf_list, buf_list->device->fd, VIDIOC_QUERYBUF, &v4l2_buf, "Can't query buffer (flags=%08x)", i);
E_LOG_INFO(buf_list, "Buffer: %d, Flags: %08x. Offset: %d", i, v4l2_buf.flags,
buf_list->do_mplanes ? v4l2_plane.m.mem_offset : v4l2_buf.m.offset);
}
error:
return -1;
}

41
device/hw/buffer_list.h Normal file
View File

@ -0,0 +1,41 @@
#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, fmt_bytesperline, fmt_interval_us;
bool do_timestamps;
uint64_t last_enqueued_us;
uint64_t last_dequeued_us;
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, unsigned bytesperline);
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);
int buffer_list_refresh_states(buffer_list_t *buf_list);

133
device/hw/buffer_lock.c Normal file
View File

@ -0,0 +1,133 @@
#include "device/hw/buffer_lock.h"
#include "device/hw/buffer_list.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)
{
uint64_t now = get_monotonic_time_us(NULL, NULL);
bool needs_buffer = false;
pthread_mutex_lock(&buf_lock->lock);
if (buf_lock->timeout_us > 0 && now - buf_lock->buf_time_us > buf_lock->timeout_us) {
buffer_consumed(buf_lock->buf, buf_lock->name);
buf_lock->buf = NULL;
}
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)
{
uint64_t now = get_monotonic_time_us(NULL, NULL);
uint64_t captured_time_us = buf ? get_time_us(CLOCK_FROM_PARAMS, NULL, &buf->v4l2_buffer.timestamp, 0) : 0;
pthread_mutex_lock(&buf_lock->lock);
if (!buf) {
buffer_consumed(buf_lock->buf, buf_lock->name);
buf_lock->buf = NULL;
buf_lock->buf_time_us = now;
} else if (now - buf_lock->buf_time_us < buf_lock->frame_interval_ms * 1000) {
buf_lock->dropped++;
E_LOG_DEBUG(buf_lock, "Dropped buffer %s (refs=%d), frame=%d/%d, frame_ms=%.1f",
dev_name(buf), buf ? buf->mmap_reflinks : 0,
buf_lock->counter, buf_lock->dropped,
(now - captured_time_us) / 1000.0f);
} else {
buffer_consumed(buf_lock->buf, buf_lock->name);
buffer_use(buf);
buf_lock->buf = buf;
buf_lock->counter++;
E_LOG_DEBUG(buf_lock, "Captured buffer %s (refs=%d), frame=%d/%d, processing_ms=%.1f, frame_ms=%.1f",
dev_name(buf), buf ? buf->mmap_reflinks : 0,
buf_lock->counter, buf_lock->dropped,
(now - captured_time_us) / 1000.0f,
(now - buf_lock->buf_time_us) / 1000.0f);
buf_lock->buf_time_us = now;
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_ms, int *counter)
{
buffer_t *buf = NULL;
struct timespec timeout;
if(!timeout_ms)
timeout_ms = DEFAULT_BUFFER_LOCK_GET_TIMEOUT;
get_time_us(CLOCK_REALTIME, &timeout, NULL, timeout_ms * 1000LL);
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;
}
int buffer_lock_write_loop(buffer_lock_t *buf_lock, int nframes, buffer_write_fn fn, void *data)
{
int counter = 0;
int frames = 0;
buffer_lock_use(buf_lock, 1);
while (nframes == 0 || frames < nframes) {
buffer_t *buf = buffer_lock_get(buf_lock, 0, &counter);
if (!buf) {
goto error;
}
int ret = fn(buf_lock, buf, frames, data);
buffer_consumed(buf, "write-loop");
if (ret > 0) {
frames++;
} else if (ret < 0) {
goto error;
}
}
ok:
buffer_lock_use(buf_lock, -1);
return frames;
error:
buffer_lock_use(buf_lock, -1);
return -frames;
}

40
device/hw/buffer_lock.h Normal file
View File

@ -0,0 +1,40 @@
#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;
uint64_t buf_time_us;
int counter;
int refs;
int dropped;
uint64_t timeout_us;
int frame_interval_ms;
} buffer_lock_t;
#define DEFAULT_BUFFER_LOCK_TIMEOUT 16 // ~60fps
#define DEFAULT_BUFFER_LOCK_GET_TIMEOUT 2000 // 2s
#define DEFINE_BUFFER_LOCK(_name, _timeout_ms) buffer_lock_t _name = { \
.name = #_name, \
.lock = PTHREAD_MUTEX_INITIALIZER, \
.cond_wait = PTHREAD_COND_INITIALIZER, \
.timeout_us = (_timeout_ms > DEFAULT_BUFFER_LOCK_TIMEOUT ? _timeout_ms : DEFAULT_BUFFER_LOCK_TIMEOUT) * 1000LL, \
};
#define DECLARE_BUFFER_LOCK(_name) extern buffer_lock_t _name;
typedef int (*buffer_write_fn)(buffer_lock_t *buf_lock, buffer_t *buf, int frame, void *data);
void buffer_lock_capture(buffer_lock_t *buf_lock, buffer_t *buf);
buffer_t *buffer_lock_get(buffer_lock_t *buf_lock, int timeout_ms, 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);
int buffer_lock_write_loop(buffer_lock_t *buf_lock, int nframes, buffer_write_fn fn, void *data);

212
device/hw/buffer_queue.c Normal file
View File

@ -0,0 +1,212 @@
#include "device/hw/buffer.h"
#include "device/hw/buffer_list.h"
#include "device/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, const char *who)
{
if (!buf) {
return false;
}
pthread_mutex_lock(&buffer_lock);
if (buf->mmap_reflinks == 0) {
E_LOG_PERROR(buf, "Non symmetric reference counts");
}
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;
}
E_LOG_DEBUG(buf, "Queuing buffer... used=%zu length=%zu (linked=%s) by %s",
buf->used,
buf->length,
buf->mmap_source ? buf->mmap_source->name : NULL,
who);
if (buf->buf_list->do_timestamps) {
get_monotonic_time_us(NULL, &buf->v4l2_buffer.timestamp);
}
E_XIOCTL(buf, buf->buf_list->device->fd, VIDIOC_QBUF, &buf->v4l2_buffer, "Can't queue buffer.");
buf->enqueued = true;
buf->enqueue_time_us = buf->buf_list->last_enqueued_us = get_monotonic_time_us(NULL, NULL);
}
pthread_mutex_unlock(&buffer_lock);
return true;
error:
{
buffer_t *mmap_source = buf->mmap_source;
buf->mmap_source = NULL;
buf->mmap_reflinks++;
pthread_mutex_unlock(&buffer_lock);
if (mmap_source) {
buffer_consumed(mmap_source, who);
}
}
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_list->bufs[i]->mmap_reflinks == 1) {
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;
}
// V4L2_BUF_FLAG_LAST
buf->v4l2_buffer.flags = 0;
buf->v4l2_buffer.flags &= ~V4L2_BUF_FLAG_KEYFRAME;
buf->v4l2_buffer.flags |= dma_buf->v4l2_buffer.flags & V4L2_BUF_FLAG_KEYFRAME;
buf->v4l2_buffer.timestamp = dma_buf->v4l2_buffer.timestamp;
if (buf_list->do_mmap) {
if (dma_buf->used > buf->length) {
E_LOG_INFO(buf_list, "The dma_buf (%s) is too long: %zu vs space=%zu",
dma_buf->name, dma_buf->used, buf->length);
dma_buf->used = buf->length;
}
uint64_t before = get_monotonic_time_us(NULL, NULL);
memcpy(buf->start, dma_buf->start, dma_buf->used);
uint64_t after = get_monotonic_time_us(NULL, NULL);
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, after-before);
} 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, "copy-data");
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 (flags=%08x)", v4l2_buf.flags);
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->v4l2_buffer.timestamp = v4l2_buf.timestamp;
buf_list->last_dequeued_us = get_monotonic_time_us(NULL, NULL);
if (buf->mmap_reflinks > 0) {
E_LOG_PERROR(buf, "Buffer appears to be enqueued? (links=%d)", buf->mmap_reflinks);
}
buf->enqueued = false;
buf->mmap_reflinks = 1;
E_LOG_DEBUG(buf_list, "Grabbed mmap buffer=%u, bytes=%d, used=%d, frame=%d, linked=%s, flags=%08x",
buf->index,
buf->length,
buf->used,
buf_list->frames,
buf->mmap_source ? buf->mmap_source->name : NULL,
buf->v4l2_buffer.flags);
if (buf->mmap_source) {
buf->mmap_source->used = 0;
buffer_consumed(buf->mmap_source, "mmap-dequeued");
buf->mmap_source = NULL;
}
buf_list->frames++;
return buf;
error:
return NULL;
}

249
device/hw/device.c Normal file
View File

@ -0,0 +1,249 @@
#include "device/hw/device.h"
#include "device/hw/buffer.h"
#include "device/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->subdev_fd = -1;
dev->allow_dma = true;
if(dev->fd < 0) {
E_LOG_ERROR(dev, "Can't open device: %s", path);
}
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);
dev->subdev_fd = device_open_v4l2_subdev(dev, 0);
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->subdev_fd >= 0) {
close(dev->subdev_fd);
}
if(dev->fd >= 0) {
close(dev->fd);
}
free(dev->name);
free(dev->path);
free(dev);
}
int device_open_buffer_list(device_t *dev, bool do_capture, unsigned width, unsigned height, unsigned format, unsigned bytesperline, int nbufs, bool do_mmap)
{
unsigned type;
char name[64];
struct buffer_list_s **buf_list = NULL;
if (!dev) {
return -1;
}
if (!dev->allow_dma) {
do_mmap = true;
}
if (do_capture) {
buf_list = &dev->capture_list;
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;
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, bytesperline) < 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_open_buffer_list_output(device_t *dev, buffer_list_t *capture_list)
{
return device_open_buffer_list(dev, false,
capture_list->fmt_width, capture_list->fmt_height,
capture_list->fmt_format, capture_list->fmt_bytesperline,
capture_list->nbufs,
capture_list->device->allow_dma ? !capture_list->do_mmap : true);
}
int device_open_buffer_list_capture(device_t *dev, buffer_list_t *output_list, float div, unsigned format, bool do_mmap)
{
if (!output_list) {
output_list = dev->output_list;
}
if (!output_list) {
return -1;
}
return device_open_buffer_list(dev, true,
output_list->fmt_width / div, output_list->fmt_height / div,
format, 0, output_list->nbufs, do_mmap);
}
int device_set_stream(device_t *dev, bool do_on)
{
struct v4l2_event_subscription sub = {0};
sub.type = V4L2_EVENT_SOURCE_CHANGE;
E_LOG_DEBUG(dev, "Subscribing to DV-timings events ...");
xioctl(dev_name(dev), dev->fd, do_on ? VIDIOC_SUBSCRIBE_EVENT : VIDIOC_UNSUBSCRIBE_EVENT, &sub);
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_set_decoder_start(device_t *dev, bool do_on)
{
struct v4l2_decoder_cmd cmd = {0};
cmd.cmd = do_on ? V4L2_DEC_CMD_START : V4L2_DEC_CMD_STOP;
E_LOG_DEBUG(dev, "Setting decoder state %s...", do_on ? "Start" : "Stop");
E_XIOCTL(dev, dev->fd, VIDIOC_DECODER_CMD, &cmd, "Cannot set decoder state");
dev->decoder_started = do_on;
return 0;
error:
return -1;
}
int device_consume_event(device_t *dev)
{
struct v4l2_event event;
if (!dev) {
return -1;
}
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_video_force_key(device_t *dev)
{
if (!dev) {
return -1;
}
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};
if (!dev) {
return -1;
}
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;
}

48
device/hw/device.h Normal file
View File

@ -0,0 +1,48 @@
#pragma once
#include "v4l2.h"
typedef struct device_s {
char *name;
char *path;
int fd;
int subdev_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;
bool paused;
bool decoder_started;
} device_t;
device_t *device_open(const char *name, const char *path);
int device_open_media_device(device_t *dev);
int device_open_v4l2_subdev(device_t *dev, int subdev);
void device_close(device_t *device);
int device_open_buffer_list(device_t *dev, bool do_capture, unsigned width, unsigned height, unsigned format, unsigned bytesperline, int nbufs, bool do_mmap);
int device_open_buffer_list_output(device_t *dev, struct buffer_list_s *capture_list);
int device_open_buffer_list_capture(device_t *dev, struct buffer_list_s *output_list, float div, unsigned format, bool do_mmap);
int device_consume_event(device_t *device);
int device_set_stream(device_t *dev, bool do_on);
int device_set_decoder_start(device_t *dev, bool do_on);
int device_video_force_key(device_t *dev);
int device_set_fps(device_t *dev, int desired_fps);
int device_set_pad_format(device_t *device, unsigned width, unsigned height, unsigned format);
int device_set_option(device_t *dev, const char *name, uint32_t id, int32_t value);
int device_set_option_string(device_t *dev, const char *option);
void device_set_option_list(device_t *dev, const char *option_list);
#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)

130
device/hw/device_media.c Normal file
View File

@ -0,0 +1,130 @@
#include "device/hw/device.h"
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <linux/media.h>
int device_open_media_device(device_t *dev)
{
struct stat st;
if (fstat(dev->fd, &st) < 0) {
E_LOG_ERROR(dev, "Cannot get fstat");
return -1;
}
if (~st.st_mode & S_IFCHR) {
E_LOG_ERROR(dev, "FD is not char");
return -1;
}
char path[256];
sprintf(path, "/sys/dev/char/%d:%d/device", major(st.st_rdev), minor(st.st_rdev));
struct dirent **namelist;
int n = scandir(path, &namelist, NULL, NULL);
if (n < 0) {
E_LOG_ERROR(dev, "Cannot scan: %s", path);
return -1;
}
int ret = -1;
while (n--) {
if (ret == -1 && strstr(namelist[n]->d_name, "media") == namelist[n]->d_name) {
path[0] = 0;
sprintf(path, "/dev/%s", namelist[n]->d_name);
ret = open(path, O_RDWR);
if (ret >= 0) {
E_LOG_VERBOSE(dev, "Opened '%s' (fd=%d)", path, ret);
}
}
free(namelist[n]);
}
free(namelist);
error:
return ret;
}
int device_open_v4l2_subdev(device_t *dev, int subdev)
{
int media_fd = -1;
unsigned int last_id = 0;
int ret = -1;
media_fd = device_open_media_device(dev);
if (media_fd < 0) {
E_LOG_ERROR(dev, "Cannot find media controller");
}
for (;;) {
struct media_entity_desc entity = {
.id = MEDIA_ENT_ID_FLAG_NEXT | last_id,
};
int rc = ioctl(media_fd, MEDIA_IOC_ENUM_ENTITIES, &entity);
if (rc < 0 && errno == EINVAL)
break;
if (rc < 0) {
goto error;
}
last_id = entity.id;
char path[256];
sprintf(path, "/sys/dev/char/%d:%d", entity.dev.major, entity.dev.minor);
char link[256];
if (readlink(path, link, sizeof(link)) < 0) {
E_LOG_ERROR(dev, "Cannot readlink '%s'", path);
goto error;
}
char * last = strrchr(link, '/');
if (!last) {
E_LOG_ERROR(dev, "Link '%s' for '%s' does not end with '/'", link, path);
goto error;
}
if (strstr(last, "/v4l-subdev") != last) {
goto error;
}
sprintf(path, "/dev%s", last);
ret = open(path, O_RDWR);
if (ret >= 0) {
E_LOG_INFO(dev, "Opened '%s' (fd=%d)", path, ret);
}
break;
}
error:
close(media_fd);
return ret;
}
int device_set_pad_format(device_t *dev, unsigned width, unsigned height, unsigned format)
{
struct v4l2_subdev_format fmt = {0};
if (dev->subdev_fd < 0) {
return -1;
}
fmt.pad = 0;
fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
fmt.format.code = format;
fmt.format.width = width;
fmt.format.height = height;
fmt.format.colorspace = V4L2_COLORSPACE_RAW;
fmt.format.field = V4L2_FIELD_ANY;
E_LOG_DEBUG(dev, "Configuring mpad %d (subdev_fd=%d)...", fmt.pad, dev->subdev_fd);
E_XIOCTL(dev, dev->subdev_fd, VIDIOC_SUBDEV_S_FMT, &fmt, "Can't configure mpad %d (subdev_fd=%d)", fmt.pad, dev->subdev_fd);
return 0;
error:
return -1;
}

129
device/hw/device_options.c Normal file
View File

@ -0,0 +1,129 @@
#include "device/hw/device.h"
#include <ctype.h>
int device_set_option(device_t *dev, const char *name, uint32_t id, int32_t value)
{
struct v4l2_control ctl = {0};
if (!dev) {
return -1;
}
ctl.id = id;
ctl.value = value;
E_LOG_DEBUG(dev, "Configuring option %s (%08x) = %d", name, id, value);
E_XIOCTL(dev, dev->subdev_fd >= 0 ? dev->subdev_fd : dev->fd, VIDIOC_S_CTRL, &ctl, "Can't set option %s", name);
return 0;
error:
return -1;
}
void device_option_normalize_name(char *in)
{
char *out = in;
while (*in) {
if (isalnum(*in)) {
*out++ = tolower(*in++);
} else if (isprint(*in)) {
*out++ = '_';
while (*++in && isprint(*in) && !isalnum(*in));
} else {
in++;
}
}
*out++ = 0;
}
int device_set_option_string_fd(device_t *dev, int fd, const char *name, const char *value)
{
struct v4l2_query_ext_ctrl qctrl = {0};
struct v4l2_control ctl = {0};
qctrl.id = V4L2_CTRL_FLAG_NEXT_CTRL;
while (0 == ioctl (fd, VIDIOC_QUERY_EXT_CTRL, &qctrl)) {
ctl.id = qctrl.id;
qctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
device_option_normalize_name(qctrl.name);
if (strcmp(qctrl.name, name) != 0)
continue;
if (qctrl.flags & V4L2_CTRL_FLAG_DISABLED) {
E_LOG_INFO(dev, "The '%s' is disabled", name);
continue;
} else if (qctrl.flags & V4L2_CTRL_FLAG_READ_ONLY) {
E_LOG_INFO(dev, "The '%s' is read-only", name);
continue;
}
switch(qctrl.type) {
case V4L2_CTRL_TYPE_INTEGER:
case V4L2_CTRL_TYPE_BOOLEAN:
case V4L2_CTRL_TYPE_MENU:
ctl.value = atoi(value);
E_LOG_VERBOSE(dev, "Configuring option %s (%08x) = %d", name, ctl.id, ctl.value);
E_XIOCTL(dev, fd, VIDIOC_S_CTRL, &ctl, "Can't set option %s", name);
return 1;
default:
E_LOG_INFO(dev, "The '%s' control type '%d' is not supported", name, qctrl.type);
return 0;
}
}
return 0;
error:
return -1;
}
int device_set_option_string(device_t *dev, const char *option)
{
char name[256];
strcpy(name, option);
char *value = strchr(name, '=');
if (!value) {
E_LOG_ERROR(dev, "Missing 'key=value': '%s'", option);
}
*value++ = 0;
device_option_normalize_name(name);
int ret = device_set_option_string_fd(dev, dev->subdev_fd, name, value);
if (ret > 0)
return 0;
ret = device_set_option_string_fd(dev, dev->fd, name, value);
if (ret == 0)
E_LOG_ERROR(dev, "The '%s' was failed to find.", option);
else if (ret < 0)
E_LOG_ERROR(dev, "The '%s' did fail to be set.", option);
return 0;
error:
return -1;
}
void device_set_option_list(device_t *dev, const char *option_list)
{
if (!dev || !option_list || !option_list[0]) {
return;
}
char *start = strdup(option_list);
char *string = start;
char *option;
while (option = strsep(&string, ",")) {
device_set_option_string(dev, option);
}
free(start);
}

285
device/hw/links.c Normal file
View File

@ -0,0 +1,285 @@
#include "device/hw/device.h"
#include "device/hw/buffer.h"
#include "device/hw/buffer_list.h"
#include "device/hw/links.h"
#define N_FDS 50
#define QUEUE_ON_CAPTURE // seems to provide better latency
int _build_fds(link_t *all_links, struct pollfd *fds, link_t **links, buffer_list_t **buf_lists, int max_n, int *max_timeout_ms)
{
int n = 0, nlinks = 0;
uint64_t now_us = get_monotonic_time_us(NULL, NULL);
for (nlinks = 0; all_links[nlinks].source; nlinks++);
// This traverses in reverse order as it requires to first fix outputs
// and go back into captures
for (int i = nlinks; i-- > 0; ) {
link_t *link = &all_links[i];
buffer_list_t *source = link->source;
if (n >= max_n) {
return -EINVAL;
}
if (!source->streaming) {
continue;
}
bool paused = true;
if (link->callbacks.check_streaming && link->callbacks.check_streaming()) {
paused = false;
}
for (int j = 0; link->sinks[j]; j++) {
buffer_list_t *sink = link->sinks[j];
if (n >= max_n) {
return -EINVAL;
}
if (!sink->streaming) {
continue;
}
int count_enqueued = buffer_list_count_enqueued(sink);
// Can something be dequeued?
fds[n].fd = sink->device->fd;
fds[n].events = POLLHUP;
if (count_enqueued > 0)
fds[n].events |= POLLOUT;
fds[n].revents = 0;
buf_lists[n] = sink;
links[n] = NULL;
n++;
// Can this chain pauses
if (!sink->device->paused && count_enqueued < sink->nbufs) {
paused = false;
}
}
source->device->paused = paused;
if (source->device->output_device) {
source->device->output_device->paused = paused;
}
int count_enqueued = buffer_list_count_enqueued(source);
bool can_dequeue = count_enqueued > 0;
#ifndef QUEUE_ON_CAPTURE
if (now_us - source->last_dequeued_us < source->fmt_interval_us) {
can_dequeue = false;
*max_timeout_ms = MIN(*max_timeout_ms, (source->last_dequeued_us + source->fmt_interval_us - now_us) / 1000);
}
#endif
fds[n].fd = source->device->fd;
fds[n].events = POLLHUP;
if (can_dequeue)
fds[n].events |= POLLIN;
fds[n].revents = 0;
buf_lists[n] = source;
links[n] = link;
n++;
}
return n;
}
int links_enqueue_from_source(buffer_list_t *buf_list, link_t *link)
{
if (!link) {
E_LOG_ERROR(buf_list, "Missing link for source");
}
buffer_t *buf = buffer_list_dequeue(buf_list);
if (!buf) {
E_LOG_ERROR(buf_list, "No buffer dequeued from source?");
}
if (link->callbacks.validate_buffer && !link->callbacks.validate_buffer(link, buf)) {
E_LOG_DEBUG(buf_list, "Buffer rejected by validation");
return 0;
}
for (int j = 0; link->sinks[j]; j++) {
if (link->sinks[j]->device->paused) {
continue;
}
buffer_list_enqueue(link->sinks[j], buf);
}
if (link->callbacks.on_buffer) {
link->callbacks.on_buffer(buf);
}
return 0;
error:
return -1;
}
int links_dequeue_from_sink(buffer_list_t *buf_list) {
buffer_t *buf = buffer_list_dequeue(buf_list);
if (!buf) {
E_LOG_ERROR(buf, "No buffer dequeued from sink?");
}
return 0;
error:
return -1;
}
void print_pollfds(struct pollfd *fds, int n)
{
if (!getenv("DEBUG_FDS")) {
return;
}
for (int i = 0; i < n; i++) {
printf("poll(i=%i, fd=%d, events=%08x, revents=%08x)\n", i, fds[i].fd, fds[i].events, fds[i].revents);
}
printf("pollfds = %d\n", n);
}
int links_step(link_t *all_links, int timeout_now_ms, int *timeout_next_ms)
{
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, &timeout_now_ms);
print_pollfds(fds, n);
int ret = poll(fds, n, timeout_now_ms);
print_pollfds(fds, n);
uint64_t now_us = get_monotonic_time_us(NULL, NULL);
if (ret < 0 && errno != EINTR) {
return errno;
}
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 event=%s%s%s%s%s%08x streaming=%d enqueued=%d/%d paused=%d",
!fds[i].revents ? "NONE/" : "",
fds[i].revents & POLLIN ? "IN/" : "",
fds[i].revents & POLLOUT ? "OUT/" : "",
fds[i].revents & POLLHUP ? "HUP/" : "",
fds[i].revents & POLLERR ? "ERR/" : "",
fds[i].revents,
buf_list->streaming,
buffer_list_count_enqueued(buf_list),
buf_list->nbufs,
buf_list->device->paused);
if (fds[i].revents & POLLIN) {
if (links_enqueue_from_source(buf_list, link) < 0) {
return -1;
}
}
// Dequeue buffers that were processed
if (fds[i].revents & POLLOUT) {
if (links_dequeue_from_sink(buf_list) < 0) {
return -1;
}
}
if (fds[i].revents & POLLHUP) {
E_LOG_INFO(buf_list, "Device disconnected.");
return -1;
}
if (fds[i].revents & POLLERR) {
E_LOG_INFO(buf_list, "Got an error");
return -1;
}
if (!buf_list->device->paused && buf_list->do_capture && buf_list->do_mmap) {
buffer_t *buf;
#ifdef QUEUE_ON_CAPTURE
if (buf_list->fmt_interval_us > 0 && now_us - buf_list->last_enqueued_us < buf_list->fmt_interval_us) {
*timeout_next_ms = MIN(*timeout_next_ms, (buf_list->last_enqueued_us + buf_list->fmt_interval_us - now_us) / 1000);
E_LOG_DEBUG(buf_list, "skipping dequeue: %.1f / %.1f. enqueued=%d",
(now_us - buf_list->last_enqueued_us) / 1000.0f,
buf_list->fmt_interval_us / 1000.0f,
buffer_list_count_enqueued(buf_list));
continue;
} else if (buf_list->fmt_interval_us > 0) {
E_LOG_DEBUG(buf_list, "since last: %.1f / %.1f. enqueued=%d",
(now_us - buf_list->last_enqueued_us) / 1000.0f,
buf_list->fmt_interval_us / 1000.0f,
buffer_list_count_enqueued(buf_list));
}
#else
// feed capture queue (two buffers)
int count_enqueued = buffer_list_count_enqueued(buf_list);
if (count_enqueued > 1)
continue;
#endif
if (buf = buffer_list_find_slot(buf_list)) {
buffer_consumed(buf, "enqueued");
}
}
}
return 0;
}
int links_stream(link_t *all_links, bool do_stream)
{
for (int i = 0; all_links[i].source; i++) {
bool streaming = true;
link_t *link = &all_links[i];
if (buffer_list_stream(link->source, streaming) < 0) {
E_LOG_ERROR(link->source, "Failed to start streaming");
}
for (int j = 0; link->sinks[j]; j++) {
if (buffer_list_stream(link->sinks[j], streaming) < 0) {
E_LOG_ERROR(link->sinks[j], "Failed to start streaming");
}
}
}
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;
}
int timeout_ms = LINKS_LOOP_INTERVAL;
while(*running) {
int timeout_now_ms = timeout_ms;
timeout_ms = LINKS_LOOP_INTERVAL;
if (links_step(all_links, timeout_now_ms, &timeout_ms) < 0) {
links_stream(all_links, false);
return -1;
}
}
links_stream(all_links, false);
return 0;
}

27
device/hw/links.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include "v4l2.h"
#define LINKS_LOOP_INTERVAL 100
typedef struct buffer_s buffer_t;
typedef struct buffer_list_s buffer_list_t;
typedef struct link_s link_t;
typedef void (*link_on_buffer)(buffer_t *buf);
typedef bool (*link_check_streaming)();
typedef bool (*link_validate_buffer)(struct link_s *link, buffer_t *buf);
typedef struct link_s {
struct buffer_list_s *source; // capture_list
struct buffer_list_s *sinks[10];
struct {
link_on_buffer on_buffer;
link_check_streaming check_streaming;
link_validate_buffer validate_buffer;
} callbacks;
} link_t;
int links_init(link_t *all_links);
int links_step(link_t *all_links, int timeout_now_ms, int *timeout_next_ms);
int links_loop(link_t *all_links, bool *running);

76
device/hw/v4l2.c Normal file
View File

@ -0,0 +1,76 @@
#include "device/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);
}
}

43
device/hw/v4l2.h Normal file
View File

@ -0,0 +1,43 @@
#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>
#include <linux/v4l2-subdev.h>
#include "opts/log.h"
#ifndef CFG_XIOCTL_RETRIES
# define CFG_XIOCTL_RETRIES 4
#endif
#define XIOCTL_RETRIES ((unsigned)(CFG_XIOCTL_RETRIES))
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)