Skip to content
Snippets Groups Projects
Commit 32911f77 authored by Azat Khasanshin's avatar Azat Khasanshin
Browse files

v4l2 plugin properties

added ability to choose device, pixel format, resolution
and frame rate
parent 5f8a6db8
No related branches found
No related tags found
No related merge requests found
......@@ -15,10 +15,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/types.h>
......@@ -29,6 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <util/threading.h>
#include <util/bmem.h>
#include <util/dstr.h>
#include <obs.h>
#define V4L2_DATA(voidptr) struct v4l2_data *data = voidptr;
......@@ -36,14 +39,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define timeval2ns(tv) \
(((uint64_t) tv.tv_sec * 1000000000) + ((uint64_t) tv.tv_usec * 1000))
static const char video[] = "/dev/video0";
struct v4l2_buffer_data {
size_t length;
void *start;
};
struct v4l2_data {
char *device;
pthread_t thread;
os_event_t event;
obs_source_t source;
......@@ -52,10 +55,41 @@ struct v4l2_data {
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;
uint_fast32_t buf_count;
struct v4l2_buffer_data *buf;
};
static enum video_format v4l2_to_obs_video_format(uint_fast32_t format)
{
switch (format) {
case V4L2_PIX_FMT_YVYU: return VIDEO_FORMAT_YVYU;
case V4L2_PIX_FMT_YUYV: return VIDEO_FORMAT_YUY2;
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;
default: return VIDEO_FORMAT_NONE;
}
}
/*
* 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;
}
/*
* start capture
*/
......@@ -212,10 +246,10 @@ static void *v4l2_thread(void *vptr)
out.color_range_max);
out.data[0] = (uint8_t *) data->buf[buf.index].start;
out.linesize[0] = data->linesize;
out.width = 640;
out.height = 480;
out.width = data->width;
out.height = data->height;
out.timestamp = timeval2ns(buf.timestamp);
out.format = VIDEO_FORMAT_YUY2;
out.format = v4l2_to_obs_video_format(data->pixelformat);
obs_source_output_video(data->source, &out);
......@@ -238,74 +272,288 @@ static const char* v4l2_getname(const char* locale)
return "V4L2 Capture Input";
}
static void v4l2_destroy(void *vptr)
static void v4l2_defaults(obs_data_t settings)
{
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));
}
/*
* List available devices
*/
static void v4l2_device_list(obs_property_t prop, obs_data_t settings)
{
DIR *dirp;
struct dirent *dp;
int fd;
struct v4l2_capability video_cap;
struct dstr device;
bool first = true;
obs_property_list_clear(prop);
dstr_init_copy(&device, "/dev/");
dirp = opendir("/sys/class/video4linux");
if (dirp) {
while ((dp = readdir(dirp)) != NULL) {
dstr_resize(&device, 5);
dstr_cat(&device, dp->d_name);
if ((fd = open(device.array,
O_RDWR | O_NONBLOCK)) == -1) {
continue;
}
if (ioctl(fd, VIDIOC_QUERYCAP, &video_cap) == -1) {
continue;
} 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_setstring(settings,
"device_id", device.array);
first = false;
}
}
close(fd);
}
closedir(dirp);
}
dstr_free(&device);
}
/*
* List formats for device
*/
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;
obs_property_list_clear(prop);
while (ioctl(dev, VIDIOC_ENUM_FMT, &fmt) == 0) {
if (v4l2_to_obs_video_format(fmt.pixelformat)
!= VIDEO_FORMAT_NONE) {
obs_property_list_add_int(prop,
(char *) fmt.description,
fmt.pixelformat);
}
fmt.index++;
}
}
/*
* List resolutions for device and format
*/
static void v4l2_resolution_list(int dev, uint_fast32_t pixelformat,
obs_property_t prop)
{
struct v4l2_frmsizeenum frmsize;
frmsize.pixel_format = pixelformat;
frmsize.index = 0;
struct dstr buffer;
dstr_init(&buffer);
obs_property_list_clear(prop);
while (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,
pack_tuple(frmsize.discrete.width,
frmsize.discrete.height));
frmsize.index++;
}
dstr_free(&buffer);
}
/*
* List framerates for device and resolution
*/
static void v4l2_framerate_list(int dev, uint_fast32_t pixelformat,
uint_fast32_t width, uint_fast32_t height, obs_property_t prop)
{
struct v4l2_frmivalenum frmival;
frmival.pixel_format = pixelformat;
frmival.width = width;
frmival.height = height;
frmival.index = 0;
struct dstr buffer;
dstr_init(&buffer);
obs_property_list_clear(prop);
while (ioctl(dev, VIDIOC_ENUM_FRAMEINTERVALS, &frmival) == 0) {
float fps = (float) frmival.discrete.denominator /
frmival.discrete.numerator;
int pack = pack_tuple(frmival.discrete.numerator,
frmival.discrete.denominator);
dstr_printf(&buffer, "%.2f", fps);
obs_property_list_add_int(prop, buffer.array, pack);
frmival.index++;
}
dstr_free(&buffer);
}
/*
* Device selected callback
*/
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"),
O_RDWR | O_NONBLOCK);
obs_property_t prop = obs_properties_get(props, "pixelformat");
v4l2_format_list(dev, prop);
obs_property_modified(prop, settings);
close(dev);
return true;
}
/*
* Format selected callback
*/
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"),
O_RDWR | O_NONBLOCK);
obs_property_t prop = obs_properties_get(props, "resolution");
v4l2_resolution_list(dev, obs_data_getint(settings, "pixelformat"),
prop);
obs_property_modified(prop, settings);
close(dev);
return true;
}
/*
* Resolution selected callback
*/
static bool resolution_selected(obs_properties_t props, obs_property_t p,
obs_data_t settings)
{
UNUSED_PARAMETER(p);
int width, height;
int dev = open(obs_data_getstring(settings, "device_id"),
O_RDWR | O_NONBLOCK);
obs_property_t prop = obs_properties_get(props, "framerate");
unpack_tuple(&width, &height, obs_data_getint(settings,
"resolution"));
v4l2_framerate_list(dev, obs_data_getint(settings, "pixelformat"),
width, height, prop);
obs_property_modified(prop, settings);
close(dev);
return true;
}
static obs_properties_t v4l2_properties(const char *locale)
{
obs_properties_t props = obs_properties_create(locale);
obs_property_t device_list = obs_properties_add_list(props, "device_id",
"Device", OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
obs_property_t format_list = obs_properties_add_list(props,
"pixelformat", "Image Format", OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_t resolution_list = obs_properties_add_list(props,
"resolution", "Resolution", OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_properties_add_list(props, "framerate", "Frame Rate",
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
v4l2_device_list(device_list, NULL);
obs_property_set_modified_callback(device_list, device_selected);
obs_property_set_modified_callback(format_list, format_selected);
obs_property_set_modified_callback(resolution_list,
resolution_selected);
return props;
}
static uint32_t v4l2_getwidth(void *vptr)
{
V4L2_DATA(vptr);
if (!data)
return;
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) {
os_event_signal(data->event);
pthread_join(data->thread, NULL);
os_event_destroy(data->event);
}
if (data->buf_count)
v4l2_destroy_mmap(data);
if (data->dev != -1)
if (data->dev != -1) {
close(data->dev);
data->dev = -1;
}
}
static void v4l2_destroy(void *vptr)
{
V4L2_DATA(vptr);
if (!data)
return;
v4l2_terminate(data);
if (data->device)
bfree(data->device);
bfree(data);
}
static void *v4l2_create(obs_data_t settings, obs_source_t source)
static void v4l2_init(struct v4l2_data *data)
{
UNUSED_PARAMETER(settings);
struct v4l2_capability cap;
struct v4l2_format fmt;
struct v4l2_streamparm par;
struct v4l2_data *data = bzalloc(sizeof(struct v4l2_data));
data->source = source;
blog(LOG_DEBUG, "v4l2-input: New input created");
data->dev = open(video, O_RDWR | O_NONBLOCK);
data->dev = open(data->device, O_RDWR | O_NONBLOCK);
if (data->dev == -1) {
blog(LOG_ERROR, "v4l2-input: Unable to open device: %s", video);
blog(LOG_ERROR, "v4l2-input: Unable to open device: %s",
data->device);
goto fail;
}
if (ioctl(data->dev, VIDIOC_QUERYCAP, &cap) < 0) {
blog(LOG_ERROR, "v4l2-input: Unable to get capabilities !");
goto fail;
}
blog(LOG_DEBUG, "v4l2-input: Got capabilities for '%s'", cap.card);
/* TODO: check if device supports needed capabilities */
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.width = data->width;
fmt.fmt.pix.height = data->height;
fmt.fmt.pix.pixelformat = data->pixelformat;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
if (ioctl(data->dev, VIDIOC_S_FMT, &fmt) < 0) {
blog(LOG_DEBUG, "v4l2-input: unable to set format");
goto fail;
}
data->pixelformat = fmt.fmt.pix.pixelformat;
data->width = fmt.fmt.pix.width;
data->height = fmt.fmt.pix.height;
par.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
par.parm.capture.timeperframe.numerator = 1;
par.parm.capture.timeperframe.denominator = 30;
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) {
blog(LOG_DEBUG, "v4l2-input: 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, "v4l2-input: Linesize: %"PRIuFAST32, data->linesize);
......@@ -319,25 +567,77 @@ static void *v4l2_create(obs_data_t settings, obs_source_t source)
goto fail;
if (pthread_create(&data->thread, NULL, v4l2_thread, data) != 0)
goto fail;
return data;
return;
fail:
v4l2_destroy(data);
return NULL;
blog(LOG_DEBUG, "v4l2-input: initialization failed");
v4l2_terminate(data);
}
static uint32_t v4l2_getwidth(void *vptr)
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) {
v4l2_device_list(NULL, 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);
restart = true;
}
if (data->pixelformat != obs_data_getint(settings, "pixelformat")) {
data->pixelformat = obs_data_getint(settings, "pixelformat");
restart = true;
}
unpack_tuple(&width, &height, obs_data_getint(settings,
"resolution"));
if (width != data->width || height != data->height) {
restart = true;
}
return 640;
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;
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);
}
}
static uint32_t v4l2_getheight(void *vptr)
static void *v4l2_create(obs_data_t settings, obs_source_t source)
{
V4L2_DATA(vptr);
UNUSED_PARAMETER(settings);
struct v4l2_data *data = bzalloc(sizeof(struct v4l2_data));
data->dev = -1;
data->source = source;
v4l2_update(data, settings);
blog(LOG_DEBUG, "v4l2-input: New input created");
return 480;
return data;
}
struct obs_source_info v4l2_input = {
......@@ -347,6 +647,9 @@ struct obs_source_info v4l2_input = {
.getname = v4l2_getname,
.create = v4l2_create,
.destroy = v4l2_destroy,
.update = v4l2_update,
.defaults = v4l2_defaults,
.properties = v4l2_properties,
.getwidth = v4l2_getwidth,
.getheight = v4l2_getheight
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment