Move all into device/
This commit is contained in:
137
device/camera.c
Normal file
137
device/camera.c
Normal 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
57
device/camera.h
Normal 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
68
device/camera_decoder.c
Normal 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
34
device/camera_direct.c
Normal 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
76
device/camera_isp.c
Normal 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;
|
||||
}
|
55
device/camera_legacy_isp.c
Normal file
55
device/camera_legacy_isp.c
Normal 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
78
device/hw/buffer.c
Normal 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
27
device/hw/buffer.h
Normal 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
260
device/hw/buffer_list.c
Normal 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
41
device/hw/buffer_list.h
Normal 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
133
device/hw/buffer_lock.c
Normal 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
40
device/hw/buffer_lock.h
Normal 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
212
device/hw/buffer_queue.c
Normal 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
249
device/hw/device.c
Normal 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
48
device/hw/device.h
Normal 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
130
device/hw/device_media.c
Normal 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
129
device/hw/device_options.c
Normal 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
285
device/hw/links.c
Normal 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
27
device/hw/links.h
Normal 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
76
device/hw/v4l2.c
Normal 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
43
device/hw/v4l2.h
Normal 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)
|
Reference in New Issue
Block a user