From d6f65be966028328ee52f0752b3dab0b58115316 Mon Sep 17 00:00:00 2001 From: fryshorts Date: Wed, 23 Jul 2014 22:18:01 +0200 Subject: [PATCH 01/10] Add libv4l2 as requirement for the v4l2 plugin. In order to convert formats not natively supported by obs the v4l2 userspace library is required. This patch adds a cmake script to find the library. --- cmake/Modules/FindLibv4l2.cmake | 34 +++++++++++++++++++++++++++++++ plugins/linux-v4l2/CMakeLists.txt | 9 ++++++++ 2 files changed, 43 insertions(+) create mode 100644 cmake/Modules/FindLibv4l2.cmake diff --git a/cmake/Modules/FindLibv4l2.cmake b/cmake/Modules/FindLibv4l2.cmake new file mode 100644 index 000000000..f4a0076a2 --- /dev/null +++ b/cmake/Modules/FindLibv4l2.cmake @@ -0,0 +1,34 @@ +# Once done these will be defined: +# +# LIBV4L2_FOUND +# LIBV4L2_INCLUDE_DIRS +# LIBV4L2_LIBRARIES +# + +if(LIBV4L2_INCLUDE_DIRS AND LIBV4L2_LIBRARIES) + set(LIBV4L2_FOUND TRUE) +else() + find_package(PkgConfig QUIET) + if (PKG_CONFIG_FOUND) + pkg_check_modules(_V4L2 QUIET v4l-utils) + endif() + + find_path(V4L2_INCLUDE_DIR + NAMES libv4l2.h + HINTS ${_V4L2_INCLUDE_DIRS} /usr/include /usr/local/include + /opt/local/include) + + find_library(V4L2_LIB + NAMES v4l2 + HINTS ${_V4L2_LIBRARY_DIRS} /usr/lib /usr/local/lib + /opt/local/lib) + + set(LIBV4L2_INCLUDE_DIRS ${V4L2_INCLUDE_DIR} + CACHE PATH "v4l2 include dir") + set(LIBV4L2_LIBRARIES "${V4L2_LIB}" + CACHE STRING "v4l2 libraries") + + find_package_handle_standard_args(LibV4L2 DEFAULT_MSG V4L2_LIB + V4L2_INCLUDE_DIR) + mark_as_advanced(V4L2_INCLUDE_DIR V4L2_LIB) +endif() diff --git a/plugins/linux-v4l2/CMakeLists.txt b/plugins/linux-v4l2/CMakeLists.txt index f2579b292..7b8b1c7af 100644 --- a/plugins/linux-v4l2/CMakeLists.txt +++ b/plugins/linux-v4l2/CMakeLists.txt @@ -1,5 +1,13 @@ include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs") +find_package(Libv4l2) +if(NOT LIBV4L2_FOUND) + message(STATUS "libv4l2 not found, disabling v4l2 plugin") + return() +endif() + +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs") + set(linux-v4l2_SOURCES linux-v4l2.c v4l2-input.c @@ -10,6 +18,7 @@ add_library(linux-v4l2 MODULE ) target_link_libraries(linux-v4l2 libobs + ${LIBV4L2_LIBRARIES} ) install_obs_plugin(linux-v4l2) From 9a573bb45649a9fa2cf8f67d1cb6c55a75c2063c Mon Sep 17 00:00:00 2001 From: fryshorts Date: Wed, 23 Jul 2014 22:34:32 +0200 Subject: [PATCH 02/10] Replace all system calls with there counterpart in libv4l2. By using the wrapper functions supplied by libv4l2 we gain support for formats not natively supported by obs. The library intercepts certain system calls to transparently enable recoding. --- plugins/linux-v4l2/v4l2-input.c | 54 +++++++++++++++++---------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c index 8f81d29cf..a2e4cac62 100644 --- a/plugins/linux-v4l2/v4l2-input.c +++ b/plugins/linux-v4l2/v4l2-input.c @@ -28,6 +28,7 @@ along with this program. If not, see . #include #include +#include #include #include @@ -158,14 +159,14 @@ static int_fast32_t v4l2_start_capture(struct v4l2_data *data) buf.memory = V4L2_MEMORY_MMAP; buf.index = i; - if (ioctl(data->dev, VIDIOC_QBUF, &buf) < 0) { + if (v4l2_ioctl(data->dev, VIDIOC_QBUF, &buf) < 0) { blog(LOG_ERROR, "unable to queue buffer"); return -1; } } type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (ioctl(data->dev, VIDIOC_STREAMON, &type) < 0) { + if (v4l2_ioctl(data->dev, VIDIOC_STREAMON, &type) < 0) { blog(LOG_ERROR, "unable to start stream"); return -1; } @@ -180,7 +181,7 @@ static int_fast32_t v4l2_stop_capture(struct v4l2_data *data) { enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if (ioctl(data->dev, VIDIOC_STREAMOFF, &type) < 0) { + if (v4l2_ioctl(data->dev, VIDIOC_STREAMOFF, &type) < 0) { blog(LOG_ERROR, "unable to stop stream"); } @@ -198,7 +199,7 @@ static int_fast32_t v4l2_create_mmap(struct v4l2_data *data) req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; - if (ioctl(data->dev, VIDIOC_REQBUFS, &req) < 0) { + if (v4l2_ioctl(data->dev, VIDIOC_REQBUFS, &req) < 0) { blog(LOG_DEBUG, "request for buffers failed !"); return -1; } @@ -218,13 +219,13 @@ static int_fast32_t v4l2_create_mmap(struct v4l2_data *data) buf.memory = V4L2_MEMORY_MMAP; buf.index = i; - if (ioctl(data->dev, VIDIOC_QUERYBUF, &buf) < 0) { + if (v4l2_ioctl(data->dev, VIDIOC_QUERYBUF, &buf) < 0) { blog(LOG_ERROR, "failed to query buffer"); return -1; } data->buf[i].length = buf.length; - data->buf[i].start = mmap(NULL, buf.length, + data->buf[i].start = v4l2_mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, data->dev, buf.m.offset); @@ -244,7 +245,7 @@ static void v4l2_destroy_mmap(struct v4l2_data *data) { for(uint_fast32_t i = 0; i < data->buf_count; ++i) { if (data->buf[i].start != MAP_FAILED) - munmap(data->buf[i].start, data->buf[i].length); + v4l2_munmap(data->buf[i].start, data->buf[i].length); } data->buf_count = 0; @@ -291,7 +292,7 @@ static void *v4l2_thread(void *vptr) buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; - if (ioctl(data->dev, VIDIOC_DQBUF, &buf) < 0) { + if (v4l2_ioctl(data->dev, VIDIOC_DQBUF, &buf) < 0) { if (errno == EAGAIN) continue; blog(LOG_DEBUG, "failed to dequeue buffer"); @@ -310,7 +311,7 @@ static void *v4l2_thread(void *vptr) obs_source_output_video(data->source, &out); - if (ioctl(data->dev, VIDIOC_QBUF, &buf) < 0) { + if (v4l2_ioctl(data->dev, VIDIOC_QBUF, &buf) < 0) { blog(LOG_DEBUG, "failed to enqueue buffer"); break; } @@ -366,12 +367,12 @@ static void v4l2_device_list(obs_property_t prop, obs_data_t settings) dstr_resize(&device, 5); dstr_cat(&device, dp->d_name); - if ((fd = open(device.array, O_RDWR | O_NONBLOCK)) == -1) { + if ((fd = v4l2_open(device.array, O_RDWR | O_NONBLOCK)) == -1) { blog(LOG_INFO, "Unable to open %s", device.array); continue; } - if (ioctl(fd, VIDIOC_QUERYCAP, &video_cap) == -1) { + if (v4l2_ioctl(fd, VIDIOC_QUERYCAP, &video_cap) == -1) { blog(LOG_INFO, "Failed to query capabilities for %s", device.array); } else if (video_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) { @@ -409,7 +410,7 @@ static void v4l2_format_list(int dev, obs_property_t prop) obs_property_list_clear(prop); - while (ioctl(dev, VIDIOC_ENUM_FMT, &fmt) == 0) { + while (v4l2_ioctl(dev, VIDIOC_ENUM_FMT, &fmt) == 0) { if (v4l2_to_obs_video_format(fmt.pixelformat) != VIDEO_FORMAT_NONE) { obs_property_list_add_int(prop, @@ -439,11 +440,11 @@ static void v4l2_resolution_list(int dev, uint_fast32_t pixelformat, obs_property_list_clear(prop); - ioctl(dev, VIDIOC_ENUM_FRAMESIZES, &frmsize); + v4l2_ioctl(dev, VIDIOC_ENUM_FRAMESIZES, &frmsize); switch(frmsize.type) { case V4L2_FRMSIZE_TYPE_DISCRETE: - while (ioctl(dev, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) { + while (v4l2_ioctl(dev, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) { dstr_printf(&buffer, "%dx%d", frmsize.discrete.width, frmsize.discrete.height); obs_property_list_add_int(prop, buffer.array, @@ -488,11 +489,12 @@ static void v4l2_framerate_list(int dev, uint_fast32_t pixelformat, obs_property_list_clear(prop); - ioctl(dev, VIDIOC_ENUM_FRAMEINTERVALS, &frmival); + v4l2_ioctl(dev, VIDIOC_ENUM_FRAMEINTERVALS, &frmival); switch(frmival.type) { case V4L2_FRMIVAL_TYPE_DISCRETE: - while (ioctl(dev, VIDIOC_ENUM_FRAMEINTERVALS, &frmival) == 0) { + while (v4l2_ioctl(dev, VIDIOC_ENUM_FRAMEINTERVALS, + &frmival) == 0) { float fps = (float) frmival.discrete.denominator / frmival.discrete.numerator; int pack = pack_tuple(frmival.discrete.numerator, @@ -529,7 +531,7 @@ static bool device_selected(obs_properties_t props, obs_property_t p, obs_data_t settings) { UNUSED_PARAMETER(p); - int dev = open(obs_data_getstring(settings, "device_id"), + int dev = v4l2_open(obs_data_getstring(settings, "device_id"), O_RDWR | O_NONBLOCK); if (dev == -1) return false; @@ -537,7 +539,7 @@ static bool device_selected(obs_properties_t props, obs_property_t p, obs_property_t prop = obs_properties_get(props, "pixelformat"); v4l2_format_list(dev, prop); obs_property_modified(prop, settings); - close(dev); + v4l2_close(dev); return true; } @@ -548,7 +550,7 @@ static bool format_selected(obs_properties_t props, obs_property_t p, obs_data_t settings) { UNUSED_PARAMETER(p); - int dev = open(obs_data_getstring(settings, "device_id"), + int dev = v4l2_open(obs_data_getstring(settings, "device_id"), O_RDWR | O_NONBLOCK); if (dev == -1) return false; @@ -557,7 +559,7 @@ static bool format_selected(obs_properties_t props, obs_property_t p, v4l2_resolution_list(dev, obs_data_getint(settings, "pixelformat"), prop); obs_property_modified(prop, settings); - close(dev); + v4l2_close(dev); return true; } @@ -569,7 +571,7 @@ static bool resolution_selected(obs_properties_t props, obs_property_t p, { UNUSED_PARAMETER(p); int width, height; - int dev = open(obs_data_getstring(settings, "device_id"), + int dev = v4l2_open(obs_data_getstring(settings, "device_id"), O_RDWR | O_NONBLOCK); if (dev == -1) return false; @@ -580,7 +582,7 @@ static bool resolution_selected(obs_properties_t props, obs_property_t p, v4l2_framerate_list(dev, obs_data_getint(settings, "pixelformat"), width, height, prop); obs_property_modified(prop, settings); - close(dev); + v4l2_close(dev); return true; } @@ -639,7 +641,7 @@ static void v4l2_terminate(struct v4l2_data *data) v4l2_destroy_mmap(data); if (data->dev != -1) { - close(data->dev); + v4l2_close(data->dev); data->dev = -1; } } @@ -663,7 +665,7 @@ static void v4l2_init(struct v4l2_data *data) struct v4l2_format fmt; struct v4l2_streamparm par; - data->dev = open(data->device, O_RDWR | O_NONBLOCK); + data->dev = v4l2_open(data->device, O_RDWR | O_NONBLOCK); if (data->dev == -1) { blog(LOG_ERROR, "Unable to open device: %s", data->device); goto fail; @@ -675,7 +677,7 @@ static void v4l2_init(struct v4l2_data *data) fmt.fmt.pix.pixelformat = data->pixelformat; fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; - if (ioctl(data->dev, VIDIOC_S_FMT, &fmt) < 0) { + if (v4l2_ioctl(data->dev, VIDIOC_S_FMT, &fmt) < 0) { blog(LOG_DEBUG, "unable to set format"); goto fail; } @@ -687,7 +689,7 @@ static void v4l2_init(struct v4l2_data *data) par.parm.capture.timeperframe.numerator = data->fps_numerator; par.parm.capture.timeperframe.denominator = data->fps_denominator; - if (ioctl(data->dev, VIDIOC_S_PARM, &par) < 0) { + if (v4l2_ioctl(data->dev, VIDIOC_S_PARM, &par) < 0) { blog(LOG_DEBUG, "unable to set params"); goto fail; } From a6bb42c3674cb716d1d170f6dc4896e1e4114949 Mon Sep 17 00:00:00 2001 From: fryshorts Date: Wed, 23 Jul 2014 22:59:48 +0200 Subject: [PATCH 03/10] Display emulated formats as such in v4l2 plugin. Formats that are emulated by v4l2 are marked by appending "(Emulated)" to them. --- plugins/linux-v4l2/v4l2-input.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c index a2e4cac62..f9f5ea07e 100644 --- a/plugins/linux-v4l2/v4l2-input.c +++ b/plugins/linux-v4l2/v4l2-input.c @@ -407,23 +407,30 @@ static void v4l2_format_list(int dev, obs_property_t prop) struct v4l2_fmtdesc fmt; fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.index = 0; + struct dstr buffer; + dstr_init(&buffer); obs_property_list_clear(prop); while (v4l2_ioctl(dev, VIDIOC_ENUM_FMT, &fmt) == 0) { + dstr_copy(&buffer, (char *) fmt.description); + if (fmt.flags & V4L2_FMT_FLAG_EMULATED) + dstr_cat(&buffer, " (Emulated)"); + if (v4l2_to_obs_video_format(fmt.pixelformat) != VIDEO_FORMAT_NONE) { - obs_property_list_add_int(prop, - (char *) fmt.description, + obs_property_list_add_int(prop, buffer.array, fmt.pixelformat); blog(LOG_INFO, "Pixelformat: %s (available)", - (char *) fmt.description); + buffer.array); } else { blog(LOG_INFO, "Pixelformat: %s (unavailable)", - (char *) fmt.description); + buffer.array); } fmt.index++; } + + dstr_free(&buffer); } /* From a5e53d5a8d1c39d13805b81567252831bb566594 Mon Sep 17 00:00:00 2001 From: fryshorts Date: Fri, 25 Jul 2014 00:17:28 +0200 Subject: [PATCH 04/10] Fix handling of multiplanar pixelformats in v4l2 plugin. The data provided by v4l2 for multiplanar formats is one consecutive block that just needs to be splitted for obs. --- plugins/linux-v4l2/v4l2-input.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c index f9f5ea07e..56ce1e7bf 100644 --- a/plugins/linux-v4l2/v4l2-input.c +++ b/plugins/linux-v4l2/v4l2-input.c @@ -75,6 +75,7 @@ static enum video_format v4l2_to_obs_video_format(uint_fast32_t format) case V4L2_PIX_FMT_UYVY: return VIDEO_FORMAT_UYVY; case V4L2_PIX_FMT_NV12: return VIDEO_FORMAT_NV12; case V4L2_PIX_FMT_YUV420: return VIDEO_FORMAT_I420; + case V4L2_PIX_FMT_YVU420: return VIDEO_FORMAT_I420; default: return VIDEO_FORMAT_NONE; } } @@ -265,9 +266,13 @@ static void *v4l2_thread(void *vptr) data->frames = 0; blog(LOG_INFO, "Started recording from %s", data->device); + uint_fast8_t cb = (data->pixelformat == V4L2_PIX_FMT_YUV420) ? 1 : 2; + uint_fast8_t cr = (cb == 1) ? 2 : 1; + while (os_event_try(data->event) == EAGAIN) { int r; fd_set fds; + uint8_t *start; struct timeval tv; struct v4l2_buffer buf; struct source_frame out; @@ -302,8 +307,32 @@ static void *v4l2_thread(void *vptr) video_format_get_parameters(VIDEO_CS_DEFAULT, VIDEO_RANGE_PARTIAL, out.color_matrix, out.color_range_min, out.color_range_max); - out.data[0] = (uint8_t *) data->buf[buf.index].start; - out.linesize[0] = data->linesize; + + start = (uint8_t *) data->buf[buf.index].start; + switch (data->pixelformat) { + case V4L2_PIX_FMT_NV12: + out.data[0] = start; + out.data[1] = start + data->linesize * data->height; + out.linesize[0] = data->linesize; + out.linesize[1] = data->linesize / 2; + break; + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_YUV420: { + out.data[0] = start; + out.data[cb] = start + data->linesize * data->height; + out.data[cr] = start + data->linesize * data->height + * 5 / 4; + out.linesize[0] = data->linesize; + out.linesize[cb] = data->linesize / 2; + out.linesize[cr] = data->linesize / 2; + break; + } + default: + out.data[0] = start; + out.linesize[0] = data->linesize; + break; + } + out.width = data->width; out.height = data->height; out.timestamp = timeval2ns(buf.timestamp); From 05ddbeb1fe996a4fdb1629de0e9edfd933191e59 Mon Sep 17 00:00:00 2001 From: fryshorts Date: Sat, 26 Jul 2014 15:42:45 +0200 Subject: [PATCH 05/10] Add function to prepare frame structure to v4l2 plugin. This adds a function to prepare the source_frame struct for use with obs_source_output_video. Since all of the values except for the timestamp and data pointers are known in before it makes little sense to compute them over and over again. Due to the fact that v4l2 uses a single continuous memory segment for multi planar formats we can also precompute memory offsets for the planes. --- plugins/linux-v4l2/v4l2-input.c | 109 +++++++++++++++++++------------- 1 file changed, 64 insertions(+), 45 deletions(-) diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c index 56ce1e7bf..5cf9adc82 100644 --- a/plugins/linux-v4l2/v4l2-input.c +++ b/plugins/linux-v4l2/v4l2-input.c @@ -253,12 +253,69 @@ static void v4l2_destroy_mmap(struct v4l2_data *data) bfree(data->buf); } +/** + * Prepare the output frame structure for obs and compute plane offsets + * + * Basically all data apart from memory pointers and the timestamp is known + * before the capture starts. This function prepares the source_frame struct + * with all the data that is already known. + * + * v4l2 uses a continuous memory segment for all planes so we simply compute + * offsets to add to the start address in order to give obs the correct data + * pointers for the individual planes. + */ +static void v4l2_prep_obs_frame(struct v4l2_data *data, + struct source_frame *frame, size_t *plane_offsets) +{ + memset(frame, 0, sizeof(struct source_frame)); + memset(plane_offsets, 0, sizeof(size_t) * MAX_AV_PLANES); + + frame->width = data->width; + frame->height = data->height; + frame->format = v4l2_to_obs_video_format(data->pixelformat); + video_format_get_parameters(VIDEO_CS_DEFAULT, VIDEO_RANGE_PARTIAL, + frame->color_matrix, frame->color_range_min, + frame->color_range_max); + + switch(data->pixelformat) { + case V4L2_PIX_FMT_NV12: + frame->linesize[0] = data->linesize; + frame->linesize[1] = data->linesize / 2; + plane_offsets[1] = data->linesize * data->height; + break; + case V4L2_PIX_FMT_YVU420: + frame->linesize[0] = data->linesize; + frame->linesize[1] = data->linesize / 2; + frame->linesize[2] = data->linesize / 2; + plane_offsets[1] = data->linesize * data->height * 5 / 4; + plane_offsets[2] = data->linesize * data->height; + break; + case V4L2_PIX_FMT_YUV420: + frame->linesize[0] = data->linesize; + frame->linesize[1] = data->linesize / 2; + frame->linesize[2] = data->linesize / 2; + plane_offsets[1] = data->linesize * data->height; + plane_offsets[2] = data->linesize * data->height * 5 / 4; + break; + default: + frame->linesize[0] = data->linesize; + break; + } +} + /* * Worker thread to get video data */ static void *v4l2_thread(void *vptr) { V4L2_DATA(vptr); + int r; + fd_set fds; + uint8_t *start; + struct timeval tv; + struct v4l2_buffer buf; + struct source_frame out; + size_t plane_offsets[MAX_AV_PLANES]; if (v4l2_start_capture(data) < 0) goto exit; @@ -266,20 +323,12 @@ static void *v4l2_thread(void *vptr) data->frames = 0; blog(LOG_INFO, "Started recording from %s", data->device); - uint_fast8_t cb = (data->pixelformat == V4L2_PIX_FMT_YUV420) ? 1 : 2; - uint_fast8_t cr = (cb == 1) ? 2 : 1; + FD_ZERO(&fds); + FD_SET(data->dev, &fds); + + v4l2_prep_obs_frame(data, &out, plane_offsets); while (os_event_try(data->event) == EAGAIN) { - int r; - fd_set fds; - uint8_t *start; - struct timeval tv; - struct v4l2_buffer buf; - struct source_frame out; - - FD_ZERO(&fds); - FD_SET(data->dev, &fds); - tv.tv_sec = 1; tv.tv_usec = 0; @@ -304,40 +353,10 @@ static void *v4l2_thread(void *vptr) break; } - video_format_get_parameters(VIDEO_CS_DEFAULT, - VIDEO_RANGE_PARTIAL, out.color_matrix, - out.color_range_min, out.color_range_max); - - start = (uint8_t *) data->buf[buf.index].start; - switch (data->pixelformat) { - case V4L2_PIX_FMT_NV12: - out.data[0] = start; - out.data[1] = start + data->linesize * data->height; - out.linesize[0] = data->linesize; - out.linesize[1] = data->linesize / 2; - break; - case V4L2_PIX_FMT_YVU420: - case V4L2_PIX_FMT_YUV420: { - out.data[0] = start; - out.data[cb] = start + data->linesize * data->height; - out.data[cr] = start + data->linesize * data->height - * 5 / 4; - out.linesize[0] = data->linesize; - out.linesize[cb] = data->linesize / 2; - out.linesize[cr] = data->linesize / 2; - break; - } - default: - out.data[0] = start; - out.linesize[0] = data->linesize; - break; - } - - out.width = data->width; - out.height = data->height; out.timestamp = timeval2ns(buf.timestamp); - out.format = v4l2_to_obs_video_format(data->pixelformat); - + start = (uint8_t *) data->buf[buf.index].start; + for (uint_fast32_t i = 0; i < MAX_AV_PLANES; ++i) + out.data[i] = start + plane_offsets[i]; obs_source_output_video(data->source, &out); if (v4l2_ioctl(data->dev, VIDIOC_QBUF, &buf) < 0) { From b629087513298fce1230f8ed3c37fdec894e7b64 Mon Sep 17 00:00:00 2001 From: fryshorts Date: Sat, 26 Jul 2014 16:50:06 +0200 Subject: [PATCH 06/10] Reorganized data struct and update function for v4l2 plugin. Due to the plugin creating a thread to retrieve and output the captured image data, care must taken to not modify data the thread reads from the outside while the thread is running. In previous revisions some settings accessed by the capture thread were written to in the update function which could cause image corruption and in the worst case crashes. The members of the data struct are now split into two groups, those that are used by the thread while it is running and must not be changed from the outside, and those can be changed at any time. --- plugins/linux-v4l2/v4l2-input.c | 100 +++++++++++++++++--------------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c index 5cf9adc82..edadc5527 100644 --- a/plugins/linux-v4l2/v4l2-input.c +++ b/plugins/linux-v4l2/v4l2-input.c @@ -47,22 +47,37 @@ struct v4l2_buffer_data { void *start; }; +/** + * Data structure for the v4l2 source + * + * The data is divided into two sections, data being used inside and outside + * the capture thread. Data used by the capture thread must not be modified + * from the outside while the thread is running. + * + * Data members prefixed with "set_" are settings from the source properties + * and may be used from outside the capture thread. + */ struct v4l2_data { - char *device; + /* data used outside of the capture thread */ + obs_source_t source; pthread_t thread; os_event_t event; - obs_source_t source; - uint_fast32_t linesize; + + char *set_device; + int_fast32_t set_pixfmt; + int_fast32_t set_res; + int_fast32_t set_fps; + + /* data used within the capture thread */ + int_fast32_t dev; uint64_t frames; - - int_fast32_t dev; - int_fast32_t pixelformat; int_fast32_t width; int_fast32_t height; - int_fast32_t fps_numerator; - int_fast32_t fps_denominator; + int_fast32_t pixfmt; + uint_fast32_t linesize; + uint_fast32_t buf_count; struct v4l2_buffer_data *buf; }; @@ -272,12 +287,12 @@ static void v4l2_prep_obs_frame(struct v4l2_data *data, frame->width = data->width; frame->height = data->height; - frame->format = v4l2_to_obs_video_format(data->pixelformat); + frame->format = v4l2_to_obs_video_format(data->pixfmt); video_format_get_parameters(VIDEO_CS_DEFAULT, VIDEO_RANGE_PARTIAL, frame->color_matrix, frame->color_range_min, frame->color_range_max); - switch(data->pixelformat) { + switch(data->pixfmt) { case V4L2_PIX_FMT_NV12: frame->linesize[0] = data->linesize; frame->linesize[1] = data->linesize / 2; @@ -321,7 +336,6 @@ static void *v4l2_thread(void *vptr) goto exit; data->frames = 0; - blog(LOG_INFO, "Started recording from %s", data->device); FD_ZERO(&fds); FD_SET(data->dev, &fds); @@ -367,8 +381,7 @@ static void *v4l2_thread(void *vptr) data->frames++; } - blog(LOG_INFO, "Stopped recording from %s after %"PRIu64" frames", - data->device, data->frames); + blog(LOG_INFO, "Stopped capture after %"PRIu64" frames", data->frames); exit: v4l2_stop_capture(data); @@ -710,8 +723,8 @@ static void v4l2_destroy(void *vptr) v4l2_terminate(data); - if (data->device) - bfree(data->device); + if (data->set_device) + bfree(data->set_device); bfree(data); } @@ -719,37 +732,39 @@ static void v4l2_init(struct v4l2_data *data) { struct v4l2_format fmt; struct v4l2_streamparm par; + int width, height; + int fps_num, fps_denom; - data->dev = v4l2_open(data->device, O_RDWR | O_NONBLOCK); + data->dev = v4l2_open(data->set_device, O_RDWR | O_NONBLOCK); if (data->dev == -1) { - blog(LOG_ERROR, "Unable to open device: %s", data->device); + blog(LOG_ERROR, "Unable to open device: %s", data->set_device); goto fail; } + unpack_tuple(&width, &height, data->set_res); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - fmt.fmt.pix.width = data->width; - fmt.fmt.pix.height = data->height; - fmt.fmt.pix.pixelformat = data->pixelformat; + fmt.fmt.pix.width = width; + fmt.fmt.pix.height = height; + fmt.fmt.pix.pixelformat = data->set_pixfmt; fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; if (v4l2_ioctl(data->dev, VIDIOC_S_FMT, &fmt) < 0) { blog(LOG_DEBUG, "unable to set format"); goto fail; } - data->pixelformat = fmt.fmt.pix.pixelformat; + data->pixfmt = fmt.fmt.pix.pixelformat; data->width = fmt.fmt.pix.width; data->height = fmt.fmt.pix.height; + unpack_tuple(&fps_num, &fps_denom, data->set_fps); par.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - par.parm.capture.timeperframe.numerator = data->fps_numerator; - par.parm.capture.timeperframe.denominator = data->fps_denominator; + par.parm.capture.timeperframe.numerator = fps_num; + par.parm.capture.timeperframe.denominator = fps_denom; if (v4l2_ioctl(data->dev, VIDIOC_S_PARM, &par) < 0) { blog(LOG_DEBUG, "unable to set params"); goto fail; } - data->fps_numerator = par.parm.capture.timeperframe.numerator; - data->fps_denominator = par.parm.capture.timeperframe.denominator; data->linesize = fmt.fmt.pix.bytesperline; blog(LOG_DEBUG, "Linesize: %"PRIuFAST32, data->linesize); @@ -759,6 +774,8 @@ static void v4l2_init(struct v4l2_data *data) goto fail; } + blog(LOG_INFO, "Start capturing from %s", data->set_device); + if (os_event_init(&data->event, OS_EVENT_TYPE_MANUAL) != 0) goto fail; if (pthread_create(&data->thread, NULL, v4l2_thread, data) != 0) @@ -774,7 +791,6 @@ static void v4l2_update(void *vptr, obs_data_t settings) V4L2_DATA(vptr); bool restart = false; const char *new_device; - int width, height, fps_num, fps_denom; new_device = obs_data_getstring(settings, "device_id"); if (strlen(new_device) == 0) { @@ -782,42 +798,30 @@ static void v4l2_update(void *vptr, obs_data_t settings) new_device = obs_data_getstring(settings, "device_id"); } - if (!data->device || strcmp(data->device, new_device) != 0) { - if (data->device) - bfree(data->device); - data->device = bstrdup(new_device); + if (!data->set_device || strcmp(data->set_device, new_device) != 0) { + if (data->set_device) + bfree(data->set_device); + data->set_device = bstrdup(new_device); restart = true; } - if (data->pixelformat != obs_data_getint(settings, "pixelformat")) { - data->pixelformat = obs_data_getint(settings, "pixelformat"); + if (data->set_pixfmt != obs_data_getint(settings, "pixelformat")) { + data->set_pixfmt = obs_data_getint(settings, "pixelformat"); restart = true; } - unpack_tuple(&width, &height, obs_data_getint(settings, - "resolution")); - if (width != data->width || height != data->height) { + if (data->set_res != obs_data_getint(settings, "resolution")) { + data->set_res = obs_data_getint(settings, "resolution"); restart = true; } - unpack_tuple(&fps_num, &fps_denom, obs_data_getint(settings, - "framerate")); - - if (fps_num != data->fps_numerator - || fps_denom != data->fps_denominator) { - data->fps_numerator = fps_num; - data->fps_denominator = fps_denom; + if (data->set_fps != obs_data_getint(settings, "framerate")) { + data->set_fps = obs_data_getint(settings, "framerate"); restart = true; } if (restart) { v4l2_terminate(data); - - /* Wait for v4l2_thread to finish before - * updating width and height */ - data->width = width; - data->height = height; - v4l2_init(data); } } From b88fe5078c6172c67d52bf4d01405b9f0ff32c81 Mon Sep 17 00:00:00 2001 From: fryshorts Date: Sat, 26 Jul 2014 16:58:03 +0200 Subject: [PATCH 07/10] Remove the getwidth/getheight functions from v4l2 plugin. Those functions are actually causing more problems than helping out with the preview because the width/height is updated immediately while the actual size of the frames displayed changes later. --- plugins/linux-v4l2/v4l2-input.c | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c index edadc5527..51e1ca84f 100644 --- a/plugins/linux-v4l2/v4l2-input.c +++ b/plugins/linux-v4l2/v4l2-input.c @@ -683,20 +683,6 @@ static obs_properties_t v4l2_properties(void) return props; } -static uint32_t v4l2_getwidth(void *vptr) -{ - V4L2_DATA(vptr); - - return data->width; -} - -static uint32_t v4l2_getheight(void *vptr) -{ - V4L2_DATA(vptr); - - return data->height; -} - static void v4l2_terminate(struct v4l2_data *data) { if (data->thread) { @@ -849,7 +835,5 @@ struct obs_source_info v4l2_input = { .destroy = v4l2_destroy, .update = v4l2_update, .defaults = v4l2_defaults, - .properties = v4l2_properties, - .getwidth = v4l2_getwidth, - .getheight = v4l2_getheight + .properties = v4l2_properties }; From 25f71fd71d837f7e3ac165269987624aff0c0eb5 Mon Sep 17 00:00:00 2001 From: fryshorts Date: Sat, 26 Jul 2014 17:26:43 +0200 Subject: [PATCH 08/10] Improved Documentation and Logging for update in v4l2 plugin. This adds some documentation to the update function and also improves the logging by adding some info and bumping errors to LOG_ERROR. --- plugins/linux-v4l2/v4l2-input.c | 46 +++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c index 51e1ca84f..12fc556f3 100644 --- a/plugins/linux-v4l2/v4l2-input.c +++ b/plugins/linux-v4l2/v4l2-input.c @@ -714,61 +714,80 @@ static void v4l2_destroy(void *vptr) bfree(data); } +/** + * Initialize the v4l2 device + * + * This function: + * - tries to open the device + * - sets pixelformat and requested resolution + * - sets the requested framerate + * - maps the buffers + * - starts the capture thread + */ static void v4l2_init(struct v4l2_data *data) { struct v4l2_format fmt; struct v4l2_streamparm par; + struct dstr fps; int width, height; int fps_num, fps_denom; + blog(LOG_INFO, "Start capture from %s", data->set_device); data->dev = v4l2_open(data->set_device, O_RDWR | O_NONBLOCK); if (data->dev == -1) { - blog(LOG_ERROR, "Unable to open device: %s", data->set_device); + blog(LOG_ERROR, "Unable to open device"); goto fail; } + /* set pixel format and resolution */ unpack_tuple(&width, &height, data->set_res); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = width; fmt.fmt.pix.height = height; fmt.fmt.pix.pixelformat = data->set_pixfmt; fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; - if (v4l2_ioctl(data->dev, VIDIOC_S_FMT, &fmt) < 0) { - blog(LOG_DEBUG, "unable to set format"); + blog(LOG_ERROR, "Unable to set format"); goto fail; } - data->pixfmt = fmt.fmt.pix.pixelformat; data->width = fmt.fmt.pix.width; data->height = fmt.fmt.pix.height; + data->pixfmt = fmt.fmt.pix.pixelformat; + data->linesize = fmt.fmt.pix.bytesperline; + blog(LOG_INFO, "Resolution: %"PRIuFAST32"x%"PRIuFAST32, + data->width, data->height); + blog(LOG_INFO, "Linesize: %"PRIuFAST32" Bytes", data->linesize); + /* set framerate */ unpack_tuple(&fps_num, &fps_denom, data->set_fps); par.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; par.parm.capture.timeperframe.numerator = fps_num; par.parm.capture.timeperframe.denominator = fps_denom; - if (v4l2_ioctl(data->dev, VIDIOC_S_PARM, &par) < 0) { - blog(LOG_DEBUG, "unable to set params"); + blog(LOG_ERROR, "Unable to set framerate"); goto fail; } + dstr_init(&fps); + dstr_printf(&fps, "%.2f", + (float) par.parm.capture.timeperframe.denominator + / par.parm.capture.timeperframe.numerator); + blog(LOG_INFO, "Framerate: %s fps", fps.array); + dstr_free(&fps); - data->linesize = fmt.fmt.pix.bytesperline; - blog(LOG_DEBUG, "Linesize: %"PRIuFAST32, data->linesize); - + /* map buffers */ if (v4l2_create_mmap(data) < 0) { - blog(LOG_ERROR, "failed to map buffers"); + blog(LOG_ERROR, "Failed to map buffers"); goto fail; } - blog(LOG_INFO, "Start capturing from %s", data->set_device); - + /* start the capture thread */ if (os_event_init(&data->event, OS_EVENT_TYPE_MANUAL) != 0) goto fail; if (pthread_create(&data->thread, NULL, v4l2_thread, data) != 0) goto fail; return; fail: - blog(LOG_DEBUG, "initialization failed"); + blog(LOG_ERROR, "Initialization failed"); v4l2_terminate(data); } @@ -821,7 +840,6 @@ static void *v4l2_create(obs_data_t settings, obs_source_t source) data->source = source; v4l2_update(data, settings); - blog(LOG_DEBUG, "New input created"); return data; } From 276a97877b0e1ef44b0c69201589a4e80c583eab Mon Sep 17 00:00:00 2001 From: fryshorts Date: Sat, 26 Jul 2014 18:57:26 +0200 Subject: [PATCH 09/10] Add const keyword to fixed lists in v4l2 plugin. This should save a little bit of memory memory. --- plugins/linux-v4l2/v4l2-input.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c index 12fc556f3..0d9fdd17a 100644 --- a/plugins/linux-v4l2/v4l2-input.c +++ b/plugins/linux-v4l2/v4l2-input.c @@ -110,7 +110,7 @@ static void unpack_tuple(int *a, int *b, int packed) } /* fixed framesizes as fallback */ -static int fixed_framesizes[] = +static const int fixed_framesizes[] = { /* 4:3 */ 160<<16 | 120, @@ -147,7 +147,7 @@ static int fixed_framesizes[] = }; /* fixed framerates as fallback */ -static int fixed_framerates[] = +static const int fixed_framerates[] = { 1<<16 | 60, 1<<16 | 50, From 273c244eff2ef22a42c174dd006ab2ef7a5bd640 Mon Sep 17 00:00:00 2001 From: fryshorts Date: Sat, 26 Jul 2014 19:55:57 +0200 Subject: [PATCH 10/10] Improved Documentation and Logging for mmap functions in v4l2 plugin. This adds some documentation to the mmap functions and also improves the logging by bumping errors to LOG_ERROR. --- plugins/linux-v4l2/v4l2-input.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/plugins/linux-v4l2/v4l2-input.c b/plugins/linux-v4l2/v4l2-input.c index 0d9fdd17a..1ae1e096c 100644 --- a/plugins/linux-v4l2/v4l2-input.c +++ b/plugins/linux-v4l2/v4l2-input.c @@ -204,8 +204,12 @@ static int_fast32_t v4l2_stop_capture(struct v4l2_data *data) return 0; } -/* - * create memory mapping for buffers +/** + * Create memory mapping for buffers + * + * This tries to map at least 2, preferably 4, buffers to userspace. + * + * @return 0 on success, -1 on failure */ static int_fast32_t v4l2_create_mmap(struct v4l2_data *data) { @@ -216,12 +220,12 @@ static int_fast32_t v4l2_create_mmap(struct v4l2_data *data) req.memory = V4L2_MEMORY_MMAP; if (v4l2_ioctl(data->dev, VIDIOC_REQBUFS, &req) < 0) { - blog(LOG_DEBUG, "request for buffers failed !"); + blog(LOG_ERROR, "Request for buffers failed !"); return -1; } if (req.count < 2) { - blog(LOG_DEBUG, "not enough memory !"); + blog(LOG_ERROR, "Device returned less than 2 buffers"); return -1; } @@ -236,7 +240,7 @@ static int_fast32_t v4l2_create_mmap(struct v4l2_data *data) buf.index = i; if (v4l2_ioctl(data->dev, VIDIOC_QUERYBUF, &buf) < 0) { - blog(LOG_ERROR, "failed to query buffer"); + blog(LOG_ERROR, "Failed to query buffer details"); return -1; } @@ -254,8 +258,8 @@ static int_fast32_t v4l2_create_mmap(struct v4l2_data *data) return 0; } -/* - * destroy memory mapping for buffers +/** + * Destroy memory mapping for buffers */ static void v4l2_destroy_mmap(struct v4l2_data *data) {