obs-studio/plugins/linux-v4l2/v4l2-output.c
Frank Löffler 8f3d4b6758 linux-v4l2: added range check for try_connect()
While the current code only ever calls try_connect() with the input
argument 'device' in the range of 0 and MAX_DEVICES, this adds a check
to ensure that future code does not break the following sprintf.

In addition, use snprintf instead of sprintf to ensure that if anything
breaks, the sprintf does not lead to memory corruption. Again, the new
check should already make sure of that, but the additional effort of
using snprintf instead of sprintf is so low that it is worth to have a
little more security in the future.
2021-01-29 09:24:42 -08:00

193 lines
4.1 KiB
C

#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;
}
bool loopback_module_available()
{
if (loopback_module_loaded()) {
return true;
}
if (system("PATH=\"$PATH:/sbin\" modinfo v4l2loopback >/dev/null 2>&1") ==
0) {
return true;
}
return false;
}
static int loopback_module_load()
{
return system(
"PATH=\"$PATH:/sbin\" 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];
if (device < 0 || device >= MAX_DEVICES)
return false;
snprintf(new_device, 16, "/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);
memset(&parm, 0, sizeof(parm));
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;
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,
};