obs-studio/plugins/linux-v4l2/v4l2-output.c
gxalpha 79822a58c3 libobs,plugins: Remove new obs_output_*2 functions
Effectively reverting parts of d314d47, this commit removes the new
functions that got added to remove the flags parameter. Instead, it just
marks the parameter as unused and documents this. Having what is
effectively an API break just to remove a parameter is a bit overkill.
The other parts of d314d47 which cleaned up the usage of the flags
parameter are untouched here.
2023-06-10 16:13:05 -07:00

282 lines
5.9 KiB
C

#define _GNU_SOURCE
#include <obs-module.h>
#include <util/dstr.h>
#include <util/platform.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>
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 is_flatpak_sandbox(void)
{
static bool flatpak_info_exists = false;
static bool initialized = false;
if (!initialized) {
flatpak_info_exists = access("/.flatpak-info", F_OK) == 0;
initialized = true;
}
return flatpak_info_exists;
}
static int run_command(const char *command)
{
struct dstr str;
int result;
dstr_init_copy(&str, "PATH=\"$PATH:/sbin\" ");
if (is_flatpak_sandbox())
dstr_cat(&str, "flatpak-spawn --host ");
dstr_cat(&str, command);
result = system(str.array);
dstr_free(&str);
return result;
}
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;
}
}
fclose(fp);
return loaded;
}
bool loopback_module_available()
{
if (loopback_module_loaded()) {
return true;
}
if (run_command("modinfo v4l2loopback >/dev/null 2>&1") == 0) {
return true;
}
return false;
}
static int loopback_module_load()
{
return run_command(
"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, const char *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;
vcam->device = open(device, O_RDWR);
if (vcam->device < 0)
return false;
if (ioctl(vcam->device, VIDIOC_QUERYCAP, &capability) < 0)
goto fail_close_device;
format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
if (ioctl(vcam->device, VIDIOC_G_FMT, &format) < 0)
goto fail_close_device;
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)
goto fail_close_device;
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)
goto fail_close_device;
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);
memset(&parm, 0, sizeof(parm));
parm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
if (ioctl(vcam->device, VIDIOC_STREAMON, &parm) < 0) {
blog(LOG_ERROR, "Failed to start streaming on '%s' (%s)",
device, strerror(errno));
goto fail_close_device;
}
blog(LOG_INFO, "Virtual camera started");
obs_output_begin_data_capture(vcam->output, 0);
return true;
fail_close_device:
close(vcam->device);
return false;
}
static int scanfilter(const struct dirent *entry)
{
return !astrcmp_n(entry->d_name, "video", 5);
}
static bool virtualcam_start(void *data)
{
struct virtualcam_data *vcam = (struct virtualcam_data *)data;
struct dirent **list;
bool success = false;
int n;
if (!loopback_module_loaded()) {
if (loopback_module_load() != 0)
return false;
}
n = scandir("/dev", &list, scanfilter,
#if defined(__linux__)
versionsort
#else
alphasort
#endif
);
if (n == -1)
return false;
for (int i = 0; i < n; i++) {
char device[32] = {0};
// Use the return value of snprintf to prevent truncation warning.
int written = snprintf(device, 32, "/dev/%s", list[i]->d_name);
if (written >= 32)
blog(LOG_DEBUG,
"v4l2-output: A format truncation may have occurred."
" This can be ignored since it is quite improbable.");
if (try_connect(vcam, device)) {
success = true;
break;
}
}
while (n--)
free(list[n]);
free(list);
if (!success)
blog(LOG_WARNING, "Failed to start virtual camera");
return success;
}
static void virtualcam_stop(void *data, uint64_t ts)
{
struct virtualcam_data *vcam = (struct virtualcam_data *)data;
obs_output_end_data_capture(vcam->output);
struct v4l2_streamparm parm = {0};
parm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
if (ioctl(vcam->device, VIDIOC_STREAMOFF, &parm) < 0) {
blog(LOG_WARNING,
"Failed to stop streaming on video device %d (%s)",
vcam->device, strerror(errno));
}
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;
uint32_t frame_size = vcam->frame_size;
while (frame_size > 0) {
ssize_t written =
write(vcam->device, frame->data[0], vcam->frame_size);
if (written == -1)
break;
frame_size -= written;
}
}
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,
};