From bfb9ddb80b9cb6d8c3a4c2c1e98d0b1ae4de9383 Mon Sep 17 00:00:00 2001 From: aramg Date: Tue, 19 Dec 2023 21:52:55 -0800 Subject: [PATCH] Update v4l2loopback handling further Set the 'keep_format' control value, like the 'v4l2loopback-ctl set-caps' tool, which results in more consistent behaviour. Extra note: there also seem to be some differences between the current main branch of v4l2loopback and the current public release v0.12.7. Both seem to work as expected now. --- src/decoder.c | 4 +- src/decoder.h | 3 ++ src/decoder_v4l2.c | 97 +++++++++++++++++++++++++++++++++++++++------- src/settings.c | 2 +- 4 files changed, 89 insertions(+), 17 deletions(-) diff --git a/src/decoder.c b/src/decoder.c index f23bbec..864b707 100644 --- a/src/decoder.c +++ b/src/decoder.c @@ -100,10 +100,10 @@ int decoder_init(const char* v4l2_device, unsigned v4l2_width, unsigned v4l2_hei set_v4l2_device(v4l2_device); droidcam_device_fd = open_v4l2_device(); } else { - droidcam_device_fd = find_v4l2_device("platform:v4l2loopback_dc"); + droidcam_device_fd = find_v4l2_device(V4L2_PLATFORM_DC); if (droidcam_device_fd < 0) { // check for generic v4l2loopback device - droidcam_device_fd = find_v4l2_device("platform:v4l2loopback"); + droidcam_device_fd = find_v4l2_device(V4L2_PLATFORM); } } diff --git a/src/decoder.h b/src/decoder.h index 646776d..bd31d78 100644 --- a/src/decoder.h +++ b/src/decoder.h @@ -63,6 +63,9 @@ void decoder_show_test_image(); #define VIDEO_FMT_DROIDCAM 3 #define VIDEO_FMT_DROIDCAMX 18 +#define V4L2_PLATFORM "platform:v4l2loopback" +#define V4L2_PLATFORM_DC "platform:v4l2loopback_dc" + void set_v4l2_device(const char* device); int open_v4l2_device(void); int find_v4l2_device(const char* bus_info); diff --git a/src/decoder_v4l2.c b/src/decoder_v4l2.c index 290adbc..0284663 100644 --- a/src/decoder_v4l2.c +++ b/src/decoder_v4l2.c @@ -22,6 +22,60 @@ static int xioctl(int fd, int request, void *arg){ return r; } +static unsigned int _get_control_id(int fd, const char *control){ + const size_t length = strnlen(control, 1024); + const unsigned next = V4L2_CTRL_FLAG_NEXT_CTRL; + struct v4l2_queryctrl qctrl; + int id; + + memset(&qctrl, 0, sizeof(qctrl)); + while (ioctl(fd, VIDIOC_QUERYCTRL, &qctrl) == 0) { + if (!strncmp((const char*)qctrl.name, control, length)) + return qctrl.id; + qctrl.id |= next; + } + for (id = V4L2_CID_USER_BASE; id < V4L2_CID_LASTP1; id++) { + qctrl.id = id; + if (ioctl(fd, VIDIOC_QUERYCTRL, &qctrl) == 0) { + if (!strncmp((const char*)qctrl.name, control, length)) + return qctrl.id; + } + } + for (qctrl.id = V4L2_CID_PRIVATE_BASE; + ioctl(fd, VIDIOC_QUERYCTRL, &qctrl) == 0; qctrl.id++) { + if (!strncmp((const char*)qctrl.name, control, length)) { + unsigned int id = qctrl.id; + return id; + } + } + return 0; +} + +static int set_control_i(int fd, const char *control, int value){ + struct v4l2_control ctrl; + memset(&ctrl, 0, sizeof(ctrl)); + ctrl.id = _get_control_id(fd, control); + ctrl.value = value; + if (ctrl.id && ioctl(fd, VIDIOC_S_CTRL, &ctrl) == 0) { + int value = ctrl.value; + return value; + } + return 0; +} +#if 0 +static int get_control_i(int fd, const char *control){ + struct v4l2_control ctrl; + memset(&ctrl, 0, sizeof(ctrl)); + ctrl.id = _get_control_id(fd, control); + + if (ctrl.id && ioctl(fd, VIDIOC_G_CTRL, &ctrl) == 0) { + int value = ctrl.value; + return value; + } + return 0; +} +#endif + int open_v4l2_device(void) { int fd; struct stat st; @@ -79,7 +133,8 @@ void set_v4l2_device(const char* device) { v4l2_device[sizeof(v4l2_device) - 1] = '\0'; } -void query_v4l_device(int droidcam_device_fd, unsigned *WEBCAM_W, unsigned *WEBCAM_H) { +void query_v4l_device(int fd, unsigned *WEBCAM_W, unsigned *WEBCAM_H) { + struct v4l2_capability v4l2cap = {0}; struct v4l2_format vid_format = {0}; vid_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; @@ -88,22 +143,35 @@ void query_v4l_device(int droidcam_device_fd, unsigned *WEBCAM_W, unsigned *WEBC *WEBCAM_W = 0; *WEBCAM_H = 0; - dbgprint("Trying to set format YU12:%dx%d\n", in_width, in_height); - vid_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; - vid_format.fmt.pix.width = in_width; - vid_format.fmt.pix.height = in_height; - vid_format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; - vid_format.fmt.pix.field = V4L2_FIELD_NONE; - xioctl(droidcam_device_fd, VIDIOC_S_FMT, &vid_format); + if (xioctl(fd, VIDIOC_QUERYCAP, &v4l2cap) < 0) { + errprint("Error: Unable to query video device. dev=%s errno=%d\n", + v4l2_device, errno); + return; + } + + dbgprint("using '%s' (%s)\n", v4l2cap.card, v4l2cap.bus_info); + + const char* bus_info_dc = V4L2_PLATFORM_DC; + if (0 != strncmp(bus_info_dc, (const char*) v4l2cap.bus_info, strlen(bus_info_dc))) { + vid_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + xioctl(fd, VIDIOC_G_FMT, &vid_format); + + dbgprint("set fmt YU12:%dx%d\n", in_width, in_height); + vid_format.fmt.pix.width = in_width; + vid_format.fmt.pix.height = in_height; + vid_format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + vid_format.fmt.pix.field = V4L2_FIELD_NONE; + if (xioctl(fd, VIDIOC_S_FMT, &vid_format) >= 0) { + set_control_i(fd, "keep_format", 1); + goto early_out; + } -#ifdef __linux__ - vid_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - xioctl(droidcam_device_fd, VIDIOC_S_FMT, &vid_format); -#endif + dbgprint("set fmt failed, trying to query fmt\n"); + } - int ret = xioctl(droidcam_device_fd, VIDIOC_G_FMT, &vid_format); + int ret = xioctl(fd, VIDIOC_G_FMT, &vid_format); if (ret < 0) { - errprint("Fatal: Unable to query video device. dev=%s errno=%d\n", + errprint("Error: Unable to determine video device fmt. dev=%s errno=%d\n", v4l2_device, errno); return; } @@ -134,6 +202,7 @@ void query_v4l_device(int droidcam_device_fd, unsigned *WEBCAM_W, unsigned *WEBC return; } +early_out: *WEBCAM_W = vid_format.fmt.pix.width; *WEBCAM_H = vid_format.fmt.pix.height; } diff --git a/src/settings.c b/src/settings.c index 9b7c6f2..c54993b 100644 --- a/src/settings.c +++ b/src/settings.c @@ -89,7 +89,7 @@ void LoadSettings(struct settings* settings) { if (1 == sscanf(buf, "audio=%d\n", &settings->audio)) continue; if (1 == sscanf(buf, "video=%d\n", &settings->video)) continue; - if (2 == sscanf(buf, "size=%dx%d\n", &arg1, &arg2)) { + if (2 == sscanf(buf, "size=%dx%d\n", &arg1, &arg2) && arg1 > 0 && arg2 > 0) { settings->v4l2_width = arg1; settings->v4l2_height = arg2; continue;