Bläddra i källkod

Merge pull request #266 from fryshorts/v4l2-input

Fix device capabilities checking and add "Leave unchanged" option for all settings.
Jim 11 år sedan
förälder
incheckning
34e2b77c0f

+ 1 - 0
plugins/linux-v4l2/data/locale/en-US.ini

@@ -4,3 +4,4 @@ Input="Input"
 ImageFormat="Video Format"
 Resolution="Resolution"
 FrameRate="Frame Rate"
+LeaveUnchanged="Leave Unchanged"

+ 76 - 0
plugins/linux-v4l2/v4l2-helpers.c

@@ -123,3 +123,79 @@ int_fast32_t v4l2_destroy_mmap(struct v4l2_buffer_data *buf)
 	return 0;
 }
 
+int_fast32_t v4l2_set_input(int_fast32_t dev, int *input)
+{
+	if (!dev || !input)
+		return -1;
+
+	return (*input == -1)
+		? v4l2_ioctl(dev, VIDIOC_G_INPUT, input)
+		: v4l2_ioctl(dev, VIDIOC_S_INPUT, input);
+}
+
+int_fast32_t v4l2_set_format(int_fast32_t dev, int *resolution,
+		int *pixelformat, int *bytesperline)
+{
+	bool set = false;
+	int width, height;
+	struct v4l2_format fmt;
+
+	if (!dev || !resolution || !pixelformat || !bytesperline)
+		return -1;
+
+	/* We need to set the type in order to query the settings */
+	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+	if (v4l2_ioctl(dev, VIDIOC_G_FMT, &fmt) < 0)
+		return -1;
+
+	if (*resolution != -1) {
+		v4l2_unpack_tuple(&width, &height, *resolution);
+		fmt.fmt.pix.width  = width;
+		fmt.fmt.pix.height = height;
+		set = true;
+	}
+
+	if (*pixelformat != -1) {
+		fmt.fmt.pix.pixelformat = *pixelformat;
+		set = true;
+	}
+
+	if (set && (v4l2_ioctl(dev, VIDIOC_S_FMT, &fmt) < 0))
+		return -1;
+
+	*resolution   = v4l2_pack_tuple(fmt.fmt.pix.width, fmt.fmt.pix.height);
+	*pixelformat  = fmt.fmt.pix.pixelformat;
+	*bytesperline = fmt.fmt.pix.bytesperline;
+	return 0;
+}
+
+int_fast32_t v4l2_set_framerate(int_fast32_t dev, int *framerate)
+{
+	bool set = false;
+	int num, denom;
+	struct v4l2_streamparm par;
+
+	if (!dev || !framerate)
+		return -1;
+
+	/* We need to set the type in order to query the stream settings */
+	par.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+	if (v4l2_ioctl(dev, VIDIOC_G_PARM, &par) < 0)
+		return -1;
+
+	if (*framerate != -1) {
+		v4l2_unpack_tuple(&num, &denom, *framerate);
+		par.parm.capture.timeperframe.numerator   = num;
+		par.parm.capture.timeperframe.denominator = denom;
+		set = true;
+	}
+
+	if (set && (v4l2_ioctl(dev, VIDIOC_S_PARM, &par) < 0))
+		return -1;
+
+	*framerate = v4l2_pack_tuple(par.parm.capture.timeperframe.numerator,
+			par.parm.capture.timeperframe.denominator);
+	return 0;
+}

+ 72 - 0
plugins/linux-v4l2/v4l2-helpers.h

@@ -131,6 +131,38 @@ static const int v4l2_framerates[] =
 	0
 };
 
+/**
+ * Pack two integer values into one
+ *
+ * Obviously the input integers have to be truncated in order to fit into
+ * one. The effective 16bits left are still enough to handle resolutions and
+ * framerates just fine.
+ *
+ * @param a integer one
+ * @param b integer two
+ *
+ * @return the packed integer
+ */
+static inline int v4l2_pack_tuple(int a, int b)
+{
+	return (a << 16) | (b & 0xffff);
+}
+
+/**
+ * Unpack two integer values from one
+ *
+ * @see v4l2_pack_tuple
+ *
+ * @param a pointer to integer a
+ * @param b pointer to integer b
+ * @param packed the packed integer
+ */
+static void v4l2_unpack_tuple(int *a, int *b, int packed)
+{
+	*a = packed >> 16;
+	*b = packed & 0xffff;
+}
+
 /**
  * Start the video capture on the device.
  *
@@ -175,6 +207,46 @@ int_fast32_t v4l2_create_mmap(int_fast32_t dev, struct v4l2_buffer_data *buf);
  */
 int_fast32_t v4l2_destroy_mmap(struct v4l2_buffer_data *buf);
 
+/**
+ * Set the video input on the device.
+ *
+ * If the action succeeds input is set to the currently selected input.
+ *
+ * @param dev handle for the v4l2 device
+ * @param input index of the input or -1 to leave it as is
+ *
+ * @return negative on failure
+ */
+int_fast32_t v4l2_set_input(int_fast32_t dev, int *input);
+
+/**
+ * Set the video format on the device.
+ *
+ * If the action succeeds resolution, pixelformat and bytesperline are set
+ * to the used values.
+ *
+ * @param dev handle for the v4l2 device
+ * @param resolution packed value of the resolution or -1 to leave as is
+ * @param pixelformat index of the pixelformat or -1 to leave as is
+ * @param bytesperline this will be set accordingly on success
+ *
+ * @return negative on failure
+ */
+int_fast32_t v4l2_set_format(int_fast32_t dev, int *resolution,
+		int *pixelformat, int *bytesperline);
+
+/**
+ * Set the framerate on the device.
+ *
+ * If the action succeeds framerate is set to the used value.
+ *
+ * @param dev handle to the v4l2 device
+ * @param framerate packed value of the framerate or -1 to leave as is
+ *
+ * @return negative on failure
+ */
+int_fast32_t v4l2_set_framerate(int_fast32_t dev, int *framerate);
+
 #ifdef __cplusplus
 }
 #endif

+ 90 - 133
plugins/linux-v4l2/v4l2-input.c

@@ -45,53 +45,30 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
  * 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 {
-	/* data used outside of the capture thread */
+	/* settings */
+	char *device_id;
+	int input;
+	int pixfmt;
+	int resolution;
+	int framerate;
+
+	/* internal data */
 	obs_source_t source;
-
 	pthread_t thread;
 	os_event_t event;
 
-	char *set_device;
-	int_fast32_t set_input;
-	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;
+	int width;
+	int height;
+	int linesize;
+	struct v4l2_buffer_data buffers;
 
+	/* statistics */
 	uint64_t frames;
-	int_fast32_t width;
-	int_fast32_t height;
-	int_fast32_t pixfmt;
-	uint_fast32_t linesize;
-
-	struct v4l2_buffer_data buffers;
 };
 
-/*
- * used to store framerate and resolution values
- */
-static int pack_tuple(int a, int b)
-{
-	return (a << 16) | (b & 0xffff);
-}
-
-static void unpack_tuple(int *a, int *b, int packed)
-{
-	*a = packed >> 16;
-	*b = packed & 0xffff;
-}
-
 /**
  * Prepare the output frame structure for obs and compute plane offsets
  *
@@ -219,11 +196,10 @@ static const char* v4l2_getname(void)
 
 static void v4l2_defaults(obs_data_t settings)
 {
-	obs_data_set_default_int(settings, "input", 0);
-	obs_data_set_default_int(settings, "pixelformat", V4L2_PIX_FMT_YUYV);
-	obs_data_set_default_int(settings, "resolution",
-			pack_tuple(640, 480));
-	obs_data_set_default_int(settings, "framerate", pack_tuple(1, 30));
+	obs_data_set_default_int(settings, "input", -1);
+	obs_data_set_default_int(settings, "pixelformat", -1);
+	obs_data_set_default_int(settings, "resolution", -1);
+	obs_data_set_default_int(settings, "framerate", -1);
 }
 
 /*
@@ -231,12 +207,11 @@ static void v4l2_defaults(obs_data_t settings)
  */
 static void v4l2_device_list(obs_property_t prop, obs_data_t settings)
 {
+	UNUSED_PARAMETER(settings);
+
 	DIR *dirp;
 	struct dirent *dp;
-	int fd;
-	struct v4l2_capability video_cap;
 	struct dstr device;
-	bool first = true;
 
 	dirp = opendir("/sys/class/video4linux");
 	if (!dirp)
@@ -247,6 +222,10 @@ static void v4l2_device_list(obs_property_t prop, obs_data_t settings)
 	dstr_init_copy(&device, "/dev/");
 
 	while ((dp = readdir(dirp)) != NULL) {
+		int fd;
+		uint32_t caps;
+		struct v4l2_capability video_cap;
+
 		if (dp->d_type == DT_DIR)
 			continue;
 
@@ -261,23 +240,26 @@ static void v4l2_device_list(obs_property_t prop, obs_data_t settings)
 		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) {
-			obs_property_list_add_string(prop,
-					(char *) video_cap.card,
-					device.array);
-			if (first) {
-				obs_data_set_string(settings,
-					"device_id", device.array);
-				first = false;
-			}
-			blog(LOG_INFO, "Found device '%s' at %s",
-			     video_cap.card, device.array);
+			close(fd);
+			continue;
 		}
-		else {
+
+		caps = (video_cap.capabilities & V4L2_CAP_DEVICE_CAPS)
+			? video_cap.device_caps
+			: video_cap.capabilities;
+
+		if (!(caps & V4L2_CAP_VIDEO_CAPTURE)) {
 			blog(LOG_INFO, "%s seems to not support video capture",
 			     device.array);
+			close(fd);
+			continue;
 		}
 
+		obs_property_list_add_string(prop, (char *) video_cap.card,
+				device.array);
+		blog(LOG_INFO, "Found device '%s' at %s", video_cap.card,
+				device.array);
+
 		close(fd);
 	}
 
@@ -295,11 +277,14 @@ static void v4l2_input_list(int_fast32_t dev, obs_property_t prop)
 
 	obs_property_list_clear(prop);
 
+	obs_property_list_add_int(prop, obs_module_text("LeaveUnchanged"), -1);
+
 	while (v4l2_ioctl(dev, VIDIOC_ENUMINPUT, &in) == 0) {
 		if (in.type & V4L2_INPUT_TYPE_CAMERA) {
 			obs_property_list_add_int(prop, (char *) in.name,
 					in.index);
-			blog(LOG_INFO, "Found input '%s'", in.name);
+			blog(LOG_INFO, "Found input '%s' (Index %d)", in.name,
+					in.index);
 		}
 		in.index++;
 	}
@@ -318,6 +303,8 @@ static void v4l2_format_list(int dev, obs_property_t prop)
 
 	obs_property_list_clear(prop);
 
+	obs_property_list_add_int(prop, obs_module_text("LeaveUnchanged"), -1);
+
 	while (v4l2_ioctl(dev, VIDIOC_ENUM_FMT, &fmt) == 0) {
 		dstr_copy(&buffer, (char *) fmt.description);
 		if (fmt.flags & V4L2_FMT_FLAG_EMULATED)
@@ -353,6 +340,8 @@ static void v4l2_resolution_list(int dev, uint_fast32_t pixelformat,
 
 	obs_property_list_clear(prop);
 
+	obs_property_list_add_int(prop, obs_module_text("LeaveUnchanged"), -1);
+
 	v4l2_ioctl(dev, VIDIOC_ENUM_FRAMESIZES, &frmsize);
 
 	switch(frmsize.type) {
@@ -361,7 +350,7 @@ static void v4l2_resolution_list(int dev, uint_fast32_t pixelformat,
 			dstr_printf(&buffer, "%dx%d", frmsize.discrete.width,
 					frmsize.discrete.height);
 			obs_property_list_add_int(prop, buffer.array,
-					pack_tuple(frmsize.discrete.width,
+					v4l2_pack_tuple(frmsize.discrete.width,
 					frmsize.discrete.height));
 			frmsize.index++;
 		}
@@ -373,7 +362,7 @@ static void v4l2_resolution_list(int dev, uint_fast32_t pixelformat,
 		for (const int *packed = v4l2_framesizes; *packed; ++packed) {
 			int width;
 			int height;
-			unpack_tuple(&width, &height, *packed);
+			v4l2_unpack_tuple(&width, &height, *packed);
 			dstr_printf(&buffer, "%dx%d", width, height);
 			obs_property_list_add_int(prop, buffer.array, *packed);
 		}
@@ -399,6 +388,8 @@ static void v4l2_framerate_list(int dev, uint_fast32_t pixelformat,
 
 	obs_property_list_clear(prop);
 
+	obs_property_list_add_int(prop, obs_module_text("LeaveUnchanged"), -1);
+
 	v4l2_ioctl(dev, VIDIOC_ENUM_FRAMEINTERVALS, &frmival);
 
 	switch(frmival.type) {
@@ -407,7 +398,7 @@ static void v4l2_framerate_list(int dev, uint_fast32_t pixelformat,
 				&frmival) == 0) {
 			float fps = (float) frmival.discrete.denominator /
 				frmival.discrete.numerator;
-			int pack = pack_tuple(frmival.discrete.numerator,
+			int pack = v4l2_pack_tuple(frmival.discrete.numerator,
 					frmival.discrete.denominator);
 			dstr_printf(&buffer, "%.2f", fps);
 			obs_property_list_add_int(prop, buffer.array, pack);
@@ -421,7 +412,7 @@ static void v4l2_framerate_list(int dev, uint_fast32_t pixelformat,
 		for (const int *packed = v4l2_framerates; *packed; ++packed) {
 			int num;
 			int denom;
-			unpack_tuple(&num, &denom, *packed);
+			v4l2_unpack_tuple(&num, &denom, *packed);
 			float fps = (float) denom / num;
 			dstr_printf(&buffer, "%.2f", fps);
 			obs_property_list_add_int(prop, buffer.array, *packed);
@@ -504,7 +495,7 @@ static bool resolution_selected(obs_properties_t props, obs_property_t p,
 		return false;
 
 	obs_property_t prop = obs_properties_get(props, "framerate");
-	unpack_tuple(&width, &height, obs_data_get_int(settings,
+	v4l2_unpack_tuple(&width, &height, obs_data_get_int(settings,
 				"resolution"));
 	v4l2_framerate_list(dev, obs_data_get_int(settings, "pixelformat"),
 			width, height, prop);
@@ -572,8 +563,8 @@ static void v4l2_destroy(void *vptr)
 
 	v4l2_terminate(data);
 
-	if (data->set_device)
-		bfree(data->set_device);
+	if (data->device_id)
+		bfree(data->device_id);
 	bfree(data);
 }
 
@@ -589,59 +580,44 @@ static void v4l2_destroy(void *vptr)
  */
 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);
+	blog(LOG_INFO, "Start capture from %s", data->device_id);
+	data->dev = v4l2_open(data->device_id, O_RDWR | O_NONBLOCK);
 	if (data->dev == -1) {
 		blog(LOG_ERROR, "Unable to open device");
 		goto fail;
 	}
 
 	/* set input */
-	if (v4l2_ioctl(data->dev, VIDIOC_S_INPUT, &data->set_input) < 0) {
-		blog(LOG_ERROR, "Unable to set input");
+	if (v4l2_set_input(data->dev, &data->input) < 0) {
+		blog(LOG_ERROR, "Unable to set input %d", data->input);
 		goto fail;
 	}
+	blog(LOG_INFO, "Input: %d", data->input);
 
 	/* 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) {
+	if (v4l2_set_format(data->dev, &data->resolution, &data->pixfmt,
+			&data->linesize) < 0) {
 		blog(LOG_ERROR, "Unable to set format");
 		goto fail;
 	}
-	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);
+	if (v4l2_to_obs_video_format(data->pixfmt) == VIDEO_FORMAT_NONE) {
+		blog(LOG_ERROR, "Selected video format not supported");
+		goto fail;
+	}
+	v4l2_unpack_tuple(&data->width, &data->height, data->resolution);
+	blog(LOG_INFO, "Resolution: %dx%d", data->width, data->height);
+	blog(LOG_INFO, "Pixelformat: %d", data->pixfmt);
+	blog(LOG_INFO, "Linesize: %d 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) {
+	if (v4l2_set_framerate(data->dev, &data->framerate) < 0) {
 		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);
+	v4l2_unpack_tuple(&fps_num, &fps_denom, data->framerate);
+	blog(LOG_INFO, "Framerate: %.2f fps", (float) fps_denom / fps_num);
 
 	/* map buffers */
 	if (v4l2_create_mmap(data->dev, &data->buffers) < 0) {
@@ -660,49 +636,30 @@ fail:
 	v4l2_terminate(data);
 }
 
+/**
+ * Update the settings for the v4l2 source
+ *
+ * Since there are very few settings that can be changed without restarting the
+ * stream we don't bother to even try. Whenever this is called the currently
+ * active stream (if exists) is stopped, the settings are updated and finally
+ * the new stream is started.
+ */
 static void v4l2_update(void *vptr, obs_data_t settings)
 {
 	V4L2_DATA(vptr);
-	bool restart = false;
-	const char *new_device;
 
-	new_device = obs_data_get_string(settings, "device_id");
-	if (strlen(new_device) == 0) {
-		v4l2_device_list(NULL, settings);
-		new_device = obs_data_get_string(settings, "device_id");
-	}
-
-	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->set_input != obs_data_get_int(settings, "input")) {
-		data->set_input = obs_data_get_int(settings, "input");
-		restart = true;
-	}
-
-	if (data->set_pixfmt != obs_data_get_int(settings, "pixelformat")) {
-		data->set_pixfmt = obs_data_get_int(settings, "pixelformat");
-		restart = true;
-	}
+	v4l2_terminate(data);
 
-	if (data->set_res != obs_data_get_int(settings, "resolution")) {
-		data->set_res = obs_data_get_int(settings, "resolution");
-		restart = true;
-	}
+	if (data->device_id)
+		bfree(data->device_id);
 
-	if (data->set_fps != obs_data_get_int(settings, "framerate")) {
-		data->set_fps = obs_data_get_int(settings, "framerate");
-		restart = true;
-	}
+	data->device_id  = bstrdup(obs_data_get_string(settings, "device_id"));
+	data->input      = obs_data_get_int(settings, "input");
+	data->pixfmt     = obs_data_get_int(settings, "pixelformat");
+	data->resolution = obs_data_get_int(settings, "resolution");
+	data->framerate  = obs_data_get_int(settings, "framerate");
 
-	if (restart) {
-		v4l2_terminate(data);
-		v4l2_init(data);
-	}
+	v4l2_init(data);
 }
 
 static void *v4l2_create(obs_data_t settings, obs_source_t source)