/****************************************************************************** obs-v4l2sink Copyright (C) 2018 by CatxFish This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ #include #include #include #include #include #include #include #include #include #include "v4l2sink.h" #include "v4l2sinkproperties.h" #define V4L2SINK_SUCCESS_OPEN 0 #define V4L2SINK_ERROR_OPEN 1 #define V4L2SINK_ERROR_FORMAT 2 #define V4L2SINK_ERROR_OTHER 3 struct v4l2sink_data{ obs_output_t *output = nullptr; bool active = false; int v4l2_fd = 0; int width = 0; int height = 0; int frame_size = 0; uint32_t format = V4L2_PIX_FMT_YUYV; }; static inline 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; case V4L2_PIX_FMT_YVU420: return VIDEO_FORMAT_I420; #ifdef V4L2_PIX_FMT_XBGR32 case V4L2_PIX_FMT_XBGR32: return VIDEO_FORMAT_BGRX; #endif case V4L2_PIX_FMT_BGR32: return VIDEO_FORMAT_BGRA; #ifdef V4L2_PIX_FMT_ABGR32 case V4L2_PIX_FMT_ABGR32: return VIDEO_FORMAT_BGRA; #endif default: return VIDEO_FORMAT_NONE; } } static inline uint32_t string_to_v4l2_format(const char* format) { if(strcmp(format, V4L2SINK_NV12)==0) return V4L2_PIX_FMT_NV12; else if(strcmp(format, V4L2SINK_YUV420)==0) return V4L2_PIX_FMT_YUV420; else if (strcmp(format, V4L2SINK_RGB32)==0) return V4L2_PIX_FMT_BGR32; else return V4L2_PIX_FMT_YUYV; } V4l2sinkProperties* prop; obs_output_t* v4l2_out; void v4l2sink_signal_init(const char *signal) { signal_handler_t *handler = v4l2sink_get_signal_handler(); signal_handler_add(handler,signal); } void v4l2sink_signal_stop(const char *msg, bool opening) { struct calldata call_data; calldata_init(&call_data); calldata_set_string(&call_data, "msg", msg); calldata_set_bool(&call_data,"opening",opening); signal_handler_t *handler = v4l2sink_get_signal_handler(); signal_handler_signal(handler, "v4l2close", &call_data); calldata_free(&call_data); } bool v4l2device_set_format(void *data,struct v4l2_format *format) { v4l2sink_data *out_data = (v4l2sink_data*)data; format->fmt.pix.width = out_data->width; format->fmt.pix.height = out_data->height; format->fmt.pix.pixelformat = out_data->format; format->fmt.pix.sizeimage = out_data->frame_size; return true; } int v4l2device_framesize(void *data) { v4l2sink_data *out_data = (v4l2sink_data*)data; switch(out_data->format){ case V4L2_PIX_FMT_YVYU: case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_UYVY: return out_data->width * out_data->height * 2; case V4L2_PIX_FMT_YUV420: case V4L2_PIX_FMT_YVU420: return out_data->width * out_data->height * 3 / 2; #ifdef V4L2_PIX_FMT_XBGR32 case V4L2_PIX_FMT_XBGR32: #endif #ifdef V4L2_PIX_FMT_ABGR32 case V4L2_PIX_FMT_ABGR32: #endif case V4L2_PIX_FMT_BGR32: return out_data->width * out_data->height * 4; } return 0; } int v4l2device_open(void *data) { v4l2sink_data *out_data = (v4l2sink_data*)data; struct v4l2_format v4l2_fmt; int width,height,ret = 0; struct v4l2_capability capability; enum video_format format; video_t *video = obs_output_video(out_data->output); obs_data_t *settings = obs_output_get_settings(out_data->output); out_data->v4l2_fd = open(obs_data_get_string(settings, "device_name") , O_RDWR); out_data->format = string_to_v4l2_format( obs_data_get_string(settings, "format")); out_data->frame_size = v4l2device_framesize(data); obs_data_release(settings); if(out_data->v4l2_fd < 0){ printf("v4l2 device open fail\n"); return V4L2SINK_ERROR_OPEN; } if (ioctl(out_data->v4l2_fd, VIDIOC_QUERYCAP, &capability) < 0){ printf("v4l2 device qureycap fail\n"); return V4L2SINK_ERROR_FORMAT; } v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; ret = ioctl(out_data->v4l2_fd, VIDIOC_G_FMT, &v4l2_fmt); if(ret<0){ printf("v4l2 device getformat fail\n"); return V4L2SINK_ERROR_FORMAT; } v4l2device_set_format(data,&v4l2_fmt); ret = ioctl(out_data->v4l2_fd, VIDIOC_S_FMT, &v4l2_fmt); if(ret<0){ printf("v4l2 device setformat fail\n"); return V4L2SINK_ERROR_FORMAT; } ret = ioctl(out_data->v4l2_fd, VIDIOC_G_FMT, &v4l2_fmt); if(ret<0){ printf("v4l2 device getformat fail\n"); return V4L2SINK_ERROR_FORMAT; } if(out_data->format != v4l2_fmt.fmt.pix.pixelformat){ printf("v4l2 format not support\n"); return V4L2SINK_ERROR_FORMAT; } width = (int32_t)obs_output_get_width(out_data->output); height = (int32_t)obs_output_get_height(out_data->output); format = v4l2_to_obs_video_format(v4l2_fmt.fmt.pix.pixelformat); if(format == VIDEO_FORMAT_NONE){ printf("v4l2 conversion format not support\n"); return V4L2SINK_ERROR_FORMAT; } if(width!= v4l2_fmt.fmt.pix.width || height!= v4l2_fmt.fmt.pix.height || format!= video_output_get_format(video)){ struct video_scale_info conv; conv.format = format; conv.width = v4l2_fmt.fmt.pix.width; conv.height = v4l2_fmt.fmt.pix.height; out_data->frame_size = v4l2_fmt.fmt.pix.sizeimage; obs_output_set_video_conversion(out_data->output,&conv); } else obs_output_set_video_conversion(out_data->output,NULL); return V4L2SINK_SUCCESS_OPEN; } static bool v4l2device_close(void *data) { v4l2sink_data *out_data = (v4l2sink_data*)data; close(out_data->v4l2_fd); } static const char *v4l2sink_getname(void *unused) { UNUSED_PARAMETER(unused); return obs_module_text("V4l2sink"); } static void v4l2sink_destroy(void *data) { v4l2sink_data *out_data = (v4l2sink_data*)data; if (out_data){ bfree(out_data); } } static void *v4l2sink_create(obs_data_t *settings, obs_output_t *output) { v4l2sink_data *data = (v4l2sink_data *)bzalloc(sizeof( struct v4l2sink_data)); data->output = output; UNUSED_PARAMETER(settings); return data; } static bool v4l2sink_start(void *data) { v4l2sink_data *out_data = (v4l2sink_data*)data; out_data->width = (int32_t)obs_output_get_width(out_data->output); out_data->height = (int32_t)obs_output_get_height(out_data->output); int ret = v4l2device_open(data); if(ret!= V4L2SINK_SUCCESS_OPEN){ switch (ret) { case V4L2SINK_ERROR_OPEN: v4l2sink_signal_stop("device open failed", true); break; case V4L2SINK_ERROR_FORMAT: v4l2sink_signal_stop("format not support", true); break; default: v4l2sink_signal_stop("device open failed", true); } return false; } if(!obs_output_can_begin_data_capture(out_data->output,0)){ v4l2sink_signal_stop("start failed", true); return false; } out_data->active = true; return obs_output_begin_data_capture(out_data->output, 0); } static void v4l2sink_stop(void *data, uint64_t ts) { v4l2sink_data *out_data = (v4l2sink_data*)data; if(out_data->active){ out_data->active = false; obs_output_end_data_capture(out_data->output); v4l2device_close(data); v4l2sink_signal_stop("stop", false); } } obs_properties_t* v4l2sink_getproperties(void *data) { UNUSED_PARAMETER(data); obs_properties_t* props = obs_properties_create(); obs_properties_set_flags(props, OBS_PROPERTIES_DEFER_UPDATE); obs_properties_add_text(props, "v4l2sink_name", obs_module_text("V4l2sink.name"), OBS_TEXT_DEFAULT); return props; } static void v4l2sink_videotick(void *param, struct video_data *frame) { v4l2sink_data *out_data = (v4l2sink_data*)param; if(out_data->active){ size_t bytes = write(out_data->v4l2_fd, frame->data[0], out_data->frame_size); } } struct obs_output_info create_output_info() { struct obs_output_info output_info = {}; output_info.id = "v4l2sink"; output_info.flags = OBS_OUTPUT_VIDEO; output_info.get_name = v4l2sink_getname; output_info.create = v4l2sink_create; output_info.destroy = v4l2sink_destroy; output_info.start = v4l2sink_start; output_info.stop = v4l2sink_stop; output_info.raw_video = v4l2sink_videotick; output_info.get_properties = v4l2sink_getproperties; return output_info; } OBS_DECLARE_MODULE() OBS_MODULE_USE_DEFAULT_LOCALE("v4l2sink", "en-US") bool obs_module_load(void) { obs_output_info v4l2sink_info = create_output_info(); obs_register_output(&v4l2sink_info); obs_data_t *settings = obs_data_create(); v4l2_out = obs_output_create("v4l2sink", "V4l2sink",settings, NULL); obs_data_release(settings); v4l2sink_signal_init("void v4l2close(string msg, bool opening)"); QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window(); QAction *action = (QAction*)obs_frontend_add_tools_menu_qaction( obs_module_text("V4l2sink")); obs_frontend_push_ui_translation(obs_module_get_string); prop = new V4l2sinkProperties(main_window); obs_frontend_pop_ui_translation(); auto menu_cb = [] { prop->setVisible(!prop->isVisible()); }; action->connect(action, &QAction::triggered, menu_cb); return true; } void obs_module_unload() { } void v4l2sink_release() { obs_output_stop(v4l2_out); obs_output_release(v4l2_out); } void v4l2sink_enable(const char *dev_name, const char *format) { obs_data_t *settings = obs_output_get_settings(v4l2_out); obs_data_set_string(settings, "device_name", dev_name); obs_data_set_string(settings, "format", format); obs_output_update(v4l2_out,settings); obs_data_release(settings); obs_output_start(v4l2_out); } void v4l2sink_disable() { obs_output_stop(v4l2_out); } signal_handler_t* v4l2sink_get_signal_handler() { return obs_output_get_signal_handler(v4l2_out); }