Browse Source

linux-v4l2: Add virtual camera output

Clayton Groeneveld 5 years ago
parent
commit
6af8726c74

+ 3 - 1
.github/workflows/main.yml

@@ -391,7 +391,9 @@ jobs:
            qtbase5-dev \
            qtbase5-dev \
            libqt5svg5-dev \
            libqt5svg5-dev \
            swig \
            swig \
-           libcmocka-dev
+           libcmocka-dev \
+           linux-generic \
+           v4l2loopback-dkms
       - name: 'Restore Chromium Embedded Framework from cache'
       - name: 'Restore Chromium Embedded Framework from cache'
         id: cef-cache
         id: cef-cache
         uses: actions/[email protected]
         uses: actions/[email protected]

+ 3 - 1
CI/install-dependencies-linux.sh

@@ -43,7 +43,9 @@ sudo apt-get install -y \
         python3-dev \
         python3-dev \
         qtbase5-dev \
         qtbase5-dev \
         libqt5svg5-dev \
         libqt5svg5-dev \
-        swig
+        swig \
+        linux-generic \
+        v4l2loopback-dkms
 
 
 # build cef
 # build cef
 wget --quiet --retry-connrefused --waitretry=1 https://cdn-fastly.obsproject.com/downloads/cef_binary_${CEF_BUILD_VERSION}_linux64.tar.bz2
 wget --quiet --retry-connrefused --waitretry=1 https://cdn-fastly.obsproject.com/downloads/cef_binary_${CEF_BUILD_VERSION}_linux64.tar.bz2

+ 1 - 0
plugins/linux-v4l2/CMakeLists.txt

@@ -39,6 +39,7 @@ set(linux-v4l2_SOURCES
 	v4l2-controls.c
 	v4l2-controls.c
 	v4l2-input.c
 	v4l2-input.c
 	v4l2-helpers.c
 	v4l2-helpers.c
+	v4l2-output.c
 	${linux-v4l2-udev_SOURCES}
 	${linux-v4l2-udev_SOURCES}
 )
 )
 
 

+ 30 - 1
plugins/linux-v4l2/linux-v4l2.c

@@ -15,18 +15,47 @@ You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 #include <obs-module.h>
 #include <obs-module.h>
+#include <util/platform.h>
 
 
 OBS_DECLARE_MODULE()
 OBS_DECLARE_MODULE()
 OBS_MODULE_USE_DEFAULT_LOCALE("linux-v4l2", "en-US")
 OBS_MODULE_USE_DEFAULT_LOCALE("linux-v4l2", "en-US")
 MODULE_EXPORT const char *obs_module_description(void)
 MODULE_EXPORT const char *obs_module_description(void)
 {
 {
-	return "Video4Linux2(V4L2) sources";
+	return "Video4Linux2(V4L2) sources/virtual camera";
 }
 }
 
 
 extern struct obs_source_info v4l2_input;
 extern struct obs_source_info v4l2_input;
+extern struct obs_output_info virtualcam_info;
+
+static bool v4l2loopback_installed()
+{
+	bool loaded = false;
+
+	int ret = system("modinfo v4l2loopback");
+
+	if (ret == 0)
+		loaded = true;
+
+	return loaded;
+}
 
 
 bool obs_module_load(void)
 bool obs_module_load(void)
 {
 {
 	obs_register_source(&v4l2_input);
 	obs_register_source(&v4l2_input);
+
+	obs_data_t *obs_settings = obs_data_create();
+
+	if (v4l2loopback_installed()) {
+		obs_register_output(&virtualcam_info);
+		obs_data_set_bool(obs_settings, "vcamEnabled", true);
+	} else {
+		obs_data_set_bool(obs_settings, "vcamEnabled", false);
+		blog(LOG_WARNING,
+		     "v4l2loopback not installed, virtual camera disabled");
+	}
+
+	obs_apply_private_data(obs_settings);
+	obs_data_release(obs_settings);
+
 	return true;
 	return true;
 }
 }

+ 179 - 0
plugins/linux-v4l2/v4l2-output.c

@@ -0,0 +1,179 @@
+#include <obs-module.h>
+#include <util/platform.h>
+#include <linux/videodev2.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#define MAX_DEVICES 64
+
+struct virtualcam_data {
+	obs_output_t *output;
+	int device;
+	uint32_t frame_size;
+};
+
+static const char *virtualcam_name(void *unused)
+{
+	UNUSED_PARAMETER(unused);
+	return "Virtual Camera Output";
+}
+
+static void virtualcam_destroy(void *data)
+{
+	struct virtualcam_data *vcam = (struct virtualcam_data *)data;
+	close(vcam->device);
+	bfree(data);
+}
+
+static bool loopback_module_loaded()
+{
+	bool loaded = false;
+
+	char temp[512];
+
+	FILE *fp = fopen("/proc/modules", "r");
+
+	if (!fp)
+		return false;
+
+	while (fgets(temp, sizeof(temp), fp)) {
+		if (strstr(temp, "v4l2loopback")) {
+			loaded = true;
+			break;
+		}
+	}
+
+	if (fp)
+		fclose(fp);
+
+	return loaded;
+}
+
+static int loopback_module_load()
+{
+	return system(
+		"pkexec modprobe v4l2loopback exclusive_caps=1 card_label='OBS Virtual Camera' && sleep 0.5");
+}
+
+static void *virtualcam_create(obs_data_t *settings, obs_output_t *output)
+{
+	struct virtualcam_data *vcam =
+		(struct virtualcam_data *)bzalloc(sizeof(*vcam));
+	vcam->output = output;
+
+	UNUSED_PARAMETER(settings);
+	return vcam;
+}
+
+static bool try_connect(void *data, int device)
+{
+	struct virtualcam_data *vcam = (struct virtualcam_data *)data;
+	struct v4l2_format format;
+	struct v4l2_capability capability;
+	struct v4l2_streamparm parm;
+
+	uint32_t width = obs_output_get_width(vcam->output);
+	uint32_t height = obs_output_get_height(vcam->output);
+
+	vcam->frame_size = width * height * 2;
+
+	char new_device[16];
+	sprintf(new_device, "/dev/video%d", device);
+
+	vcam->device = open(new_device, O_RDWR);
+
+	if (vcam->device < 0)
+		return false;
+
+	if (ioctl(vcam->device, VIDIOC_QUERYCAP, &capability) < 0)
+		return false;
+
+	format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+
+	if (ioctl(vcam->device, VIDIOC_G_FMT, &format) < 0)
+		return false;
+
+	struct obs_video_info ovi;
+	obs_get_video_info(&ovi);
+
+	parm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+
+	parm.parm.output.capability = V4L2_CAP_TIMEPERFRAME;
+	parm.parm.output.timeperframe.numerator = ovi.fps_den;
+	parm.parm.output.timeperframe.denominator = ovi.fps_num;
+	parm.parm.output.outputmode = 0;
+	parm.parm.output.writebuffers = 0;
+	parm.parm.output.extendedmode = 0;
+	parm.parm.output.reserved[4] = 0;
+
+	if (ioctl(vcam->device, VIDIOC_S_PARM, &parm) < 0)
+		return false;
+
+	format.fmt.pix.width = width;
+	format.fmt.pix.height = height;
+	format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
+	format.fmt.pix.sizeimage = vcam->frame_size;
+
+	if (ioctl(vcam->device, VIDIOC_S_FMT, &format) < 0)
+		return false;
+
+	struct video_scale_info vsi = {0};
+	vsi.format = VIDEO_FORMAT_YUY2;
+	vsi.width = width;
+	vsi.height = height;
+	obs_output_set_video_conversion(vcam->output, &vsi);
+
+	blog(LOG_INFO, "Virtual camera started");
+	obs_output_begin_data_capture(vcam->output, 0);
+
+	return true;
+}
+
+static bool virtualcam_start(void *data)
+{
+	struct virtualcam_data *vcam = (struct virtualcam_data *)data;
+
+	if (!loopback_module_loaded()) {
+		if (loopback_module_load() != 0)
+			return false;
+	}
+
+	for (int i = 0; i < MAX_DEVICES; i++) {
+		if (!try_connect(vcam, i))
+			continue;
+		else
+			return true;
+	}
+
+	blog(LOG_WARNING, "Failed to start virtual camera");
+	return false;
+}
+
+static void virtualcam_stop(void *data, uint64_t ts)
+{
+	struct virtualcam_data *vcam = (struct virtualcam_data *)data;
+	obs_output_end_data_capture(vcam->output);
+	close(vcam->device);
+
+	blog(LOG_INFO, "Virtual camera stopped");
+
+	UNUSED_PARAMETER(ts);
+}
+
+static void virtual_video(void *param, struct video_data *frame)
+{
+	struct virtualcam_data *vcam = (struct virtualcam_data *)param;
+	write(vcam->device, frame->data[0], vcam->frame_size);
+}
+
+struct obs_output_info virtualcam_info = {
+	.id = "virtualcam_output",
+	.flags = OBS_OUTPUT_VIDEO,
+	.get_name = virtualcam_name,
+	.create = virtualcam_create,
+	.destroy = virtualcam_destroy,
+	.start = virtualcam_start,
+	.stop = virtualcam_stop,
+	.raw_video = virtual_video,
+};