1 /******************************************************************************
2     obs-v4l2sink
3     Copyright (C) 2018 by CatxFish
4     This program is free software: you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation, either version 2 of the License, or
7     (at your option) any later version.
8     This program is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11     GNU General Public License for more details.
12     You should have received a copy of the GNU General Public License
13     along with this program.  If not, see <http://www.gnu.org/licenses/>.
14 ******************************************************************************/
15 
16 
17 #include <obs-frontend-api.h>
18 #include <obs-module.h>
19 #include <QMainWindow>
20 #include <QAction>
21 #include <linux/videodev2.h>
22 #include <sys/ioctl.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <stdio.h>
26 #include "v4l2sink.h"
27 #include "v4l2sinkproperties.h"
28 
29 #define V4L2SINK_SUCCESS_OPEN  0
30 #define V4L2SINK_ERROR_OPEN    1
31 #define V4L2SINK_ERROR_FORMAT  2
32 #define V4L2SINK_ERROR_OTHER   3
33 
34 struct v4l2sink_data{
35 	obs_output_t *output = nullptr;
36 	bool active = false;
37 	int v4l2_fd = 0;
38 	int width = 0;
39 	int height = 0;
40 	int frame_size = 0;
41 	uint32_t format = V4L2_PIX_FMT_YUYV;
42 };
43 
v4l2_to_obs_video_format(uint_fast32_t format)44 static inline enum video_format v4l2_to_obs_video_format(uint_fast32_t format)
45 {
46 	switch (format) {
47 	case V4L2_PIX_FMT_YVYU:   return VIDEO_FORMAT_YVYU;
48 	case V4L2_PIX_FMT_YUYV:   return VIDEO_FORMAT_YUY2;
49 	case V4L2_PIX_FMT_UYVY:   return VIDEO_FORMAT_UYVY;
50 	case V4L2_PIX_FMT_NV12:   return VIDEO_FORMAT_NV12;
51 	case V4L2_PIX_FMT_YUV420: return VIDEO_FORMAT_I420;
52 	case V4L2_PIX_FMT_YVU420: return VIDEO_FORMAT_I420;
53 #ifdef V4L2_PIX_FMT_XBGR32
54 	case V4L2_PIX_FMT_XBGR32: return VIDEO_FORMAT_BGRX;
55 #endif
56 	case V4L2_PIX_FMT_BGR32:  return VIDEO_FORMAT_BGRA;
57 #ifdef V4L2_PIX_FMT_ABGR32
58 	case V4L2_PIX_FMT_ABGR32: return VIDEO_FORMAT_BGRA;
59 #endif
60 	default:                  return VIDEO_FORMAT_NONE;
61 	}
62 }
63 
string_to_v4l2_format(const char * format)64 static inline uint32_t string_to_v4l2_format(const char* format)
65 {
66 	if(strcmp(format, V4L2SINK_NV12)==0)
67 		return V4L2_PIX_FMT_NV12;
68 	else if(strcmp(format, V4L2SINK_YUV420)==0)
69 		return V4L2_PIX_FMT_YUV420;
70 	else if (strcmp(format, V4L2SINK_RGB32)==0)
71 		return V4L2_PIX_FMT_BGR32;
72 	else
73 		return V4L2_PIX_FMT_YUYV;
74 }
75 
76 V4l2sinkProperties* prop;
77 obs_output_t* v4l2_out;
78 
v4l2sink_signal_init(const char * signal)79 void v4l2sink_signal_init(const char *signal)
80 {
81 	signal_handler_t *handler = v4l2sink_get_signal_handler();
82 	signal_handler_add(handler,signal);
83 }
84 
v4l2sink_signal_stop(const char * msg,bool opening)85 void v4l2sink_signal_stop(const char *msg, bool opening)
86 {
87 	struct calldata call_data;
88 	calldata_init(&call_data);
89 	calldata_set_string(&call_data, "msg", msg);
90 	calldata_set_bool(&call_data,"opening",opening);
91 	signal_handler_t *handler = v4l2sink_get_signal_handler();
92 	signal_handler_signal(handler, "v4l2close", &call_data);
93 	calldata_free(&call_data);
94 }
95 
v4l2device_set_format(void * data,struct v4l2_format * format)96 bool v4l2device_set_format(void *data,struct v4l2_format *format)
97 {
98 	v4l2sink_data *out_data = (v4l2sink_data*)data;
99 	format->fmt.pix.width = out_data->width;
100 	format->fmt.pix.height = out_data->height;
101 	format->fmt.pix.pixelformat = out_data->format;
102 	format->fmt.pix.sizeimage = out_data->frame_size;
103 	return true;
104 }
105 
v4l2device_framesize(void * data)106 int v4l2device_framesize(void *data)
107 {
108 	v4l2sink_data *out_data = (v4l2sink_data*)data;
109 	switch(out_data->format){
110 
111 	case V4L2_PIX_FMT_YVYU:
112 	case V4L2_PIX_FMT_YUYV:
113 	case V4L2_PIX_FMT_UYVY:
114 		return out_data->width * out_data->height * 2;
115 	case V4L2_PIX_FMT_YUV420:
116 	case V4L2_PIX_FMT_YVU420:
117 		return out_data->width * out_data->height * 3 / 2;
118 #ifdef V4L2_PIX_FMT_XBGR32
119 	case V4L2_PIX_FMT_XBGR32:
120 #endif
121 #ifdef V4L2_PIX_FMT_ABGR32
122 	case V4L2_PIX_FMT_ABGR32:
123 #endif
124 	case V4L2_PIX_FMT_BGR32:
125 		return out_data->width * out_data->height * 4;
126 	}
127 	return 0;
128 }
129 
v4l2device_open(void * data)130 int v4l2device_open(void *data)
131 {
132 	v4l2sink_data *out_data = (v4l2sink_data*)data;
133 	struct v4l2_format v4l2_fmt;
134 	int width,height,ret = 0;
135 	struct v4l2_capability capability;
136 	enum video_format format;
137 	video_t *video = obs_output_video(out_data->output);
138 
139 	obs_data_t *settings = obs_output_get_settings(out_data->output);
140 	out_data->v4l2_fd = open(obs_data_get_string(settings, "device_name")
141 		, O_RDWR);
142 	out_data->format = string_to_v4l2_format(
143 		obs_data_get_string(settings, "format"));
144 	out_data->frame_size = v4l2device_framesize(data);
145 	obs_data_release(settings);
146 
147 	if(out_data->v4l2_fd  < 0){
148 		printf("v4l2 device open fail\n");
149 		return V4L2SINK_ERROR_OPEN;
150 	}
151 
152 	if (ioctl(out_data->v4l2_fd, VIDIOC_QUERYCAP, &capability) < 0){
153 		printf("v4l2 device qureycap fail\n");
154 		return V4L2SINK_ERROR_FORMAT;
155 	}
156 
157 	v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
158 	ret = ioctl(out_data->v4l2_fd, VIDIOC_G_FMT, &v4l2_fmt);
159 
160 	if(ret<0){
161 		printf("v4l2 device getformat fail\n");
162 		return V4L2SINK_ERROR_FORMAT;
163 	}
164 
165 	v4l2device_set_format(data,&v4l2_fmt);
166 	ret = ioctl(out_data->v4l2_fd, VIDIOC_S_FMT, &v4l2_fmt);
167 
168 	if(ret<0){
169 		printf("v4l2 device setformat fail\n");
170 		return V4L2SINK_ERROR_FORMAT;
171 	}
172 
173 	ret = ioctl(out_data->v4l2_fd, VIDIOC_G_FMT, &v4l2_fmt);
174 
175 	if(ret<0){
176 		printf("v4l2 device getformat fail\n");
177 		return V4L2SINK_ERROR_FORMAT;
178 	}
179 
180 	if(out_data->format != v4l2_fmt.fmt.pix.pixelformat){
181 		printf("v4l2 format not support\n");
182 		return V4L2SINK_ERROR_FORMAT;
183 	}
184 
185 
186 	width = (int32_t)obs_output_get_width(out_data->output);
187 	height = (int32_t)obs_output_get_height(out_data->output);
188 	format = v4l2_to_obs_video_format(v4l2_fmt.fmt.pix.pixelformat);
189 
190 	if(format == VIDEO_FORMAT_NONE){
191 		printf("v4l2 conversion format not support\n");
192 		return V4L2SINK_ERROR_FORMAT;
193 	}
194 
195 	if(width!= v4l2_fmt.fmt.pix.width ||
196 	height!= v4l2_fmt.fmt.pix.height ||
197 	format!= video_output_get_format(video)){
198 		struct video_scale_info conv;
199 		conv.format = format;
200 		conv.width = v4l2_fmt.fmt.pix.width;
201 		conv.height = v4l2_fmt.fmt.pix.height;
202 		out_data->frame_size = v4l2_fmt.fmt.pix.sizeimage;
203 		obs_output_set_video_conversion(out_data->output,&conv);
204 	}
205 	else
206 		obs_output_set_video_conversion(out_data->output,NULL);
207 
208 	return V4L2SINK_SUCCESS_OPEN;
209 }
210 
211 
212 
v4l2device_close(void * data)213 static bool v4l2device_close(void *data)
214 {
215 	v4l2sink_data *out_data = (v4l2sink_data*)data;
216 	close(out_data->v4l2_fd);
217 }
218 
v4l2sink_getname(void * unused)219 static const char *v4l2sink_getname(void *unused)
220 {
221 	UNUSED_PARAMETER(unused);
222 	return obs_module_text("V4l2sink");
223 }
224 
v4l2sink_destroy(void * data)225 static void v4l2sink_destroy(void *data)
226 {
227 	v4l2sink_data *out_data = (v4l2sink_data*)data;
228 	if (out_data){
229 		bfree(out_data);
230 	}
231 }
v4l2sink_create(obs_data_t * settings,obs_output_t * output)232 static void *v4l2sink_create(obs_data_t *settings, obs_output_t *output)
233 {
234 	v4l2sink_data *data = (v4l2sink_data *)bzalloc(sizeof(
235 		struct v4l2sink_data));
236 	data->output = output;
237 	UNUSED_PARAMETER(settings);
238 	return data;
239 }
240 
v4l2sink_start(void * data)241 static bool v4l2sink_start(void *data)
242 {
243 	v4l2sink_data *out_data = (v4l2sink_data*)data;
244 	out_data->width = (int32_t)obs_output_get_width(out_data->output);
245 	out_data->height = (int32_t)obs_output_get_height(out_data->output);
246 	int ret = v4l2device_open(data);
247 
248 	if(ret!= V4L2SINK_SUCCESS_OPEN){
249 		switch (ret) {
250 		case V4L2SINK_ERROR_OPEN:
251 			v4l2sink_signal_stop("device open failed", true);
252 			break;
253 		case V4L2SINK_ERROR_FORMAT:
254 			v4l2sink_signal_stop("format not support", true);
255 			break;
256 		default:
257 			v4l2sink_signal_stop("device open failed", true);
258 		}
259 		return false;
260 	}
261 
262 	if(!obs_output_can_begin_data_capture(out_data->output,0)){
263 		v4l2sink_signal_stop("start failed", true);
264 		return false;
265 	}
266 
267 	out_data->active = true;
268 	return obs_output_begin_data_capture(out_data->output, 0);
269 }
270 
v4l2sink_stop(void * data,uint64_t ts)271 static void v4l2sink_stop(void *data, uint64_t ts)
272 {
273 	v4l2sink_data *out_data = (v4l2sink_data*)data;
274 
275 	if(out_data->active){
276 		out_data->active = false;
277 		obs_output_end_data_capture(out_data->output);
278 		v4l2device_close(data);
279 		v4l2sink_signal_stop("stop", false);
280 	}
281 
282 }
283 
v4l2sink_getproperties(void * data)284 obs_properties_t* v4l2sink_getproperties(void *data)
285 {
286 	UNUSED_PARAMETER(data);
287 
288 	obs_properties_t* props = obs_properties_create();
289 	obs_properties_set_flags(props, OBS_PROPERTIES_DEFER_UPDATE);
290 
291 	obs_properties_add_text(props, "v4l2sink_name",
292 		obs_module_text("V4l2sink.name"), OBS_TEXT_DEFAULT);
293 	return props;
294 }
295 
v4l2sink_videotick(void * param,struct video_data * frame)296 static void v4l2sink_videotick(void *param, struct video_data *frame)
297 {
298 	v4l2sink_data *out_data = (v4l2sink_data*)param;
299 	if(out_data->active){
300 		size_t bytes = write(out_data->v4l2_fd, frame->data[0],
301 			out_data->frame_size);
302 	}
303 }
304 
create_output_info()305 struct obs_output_info create_output_info()
306 {
307 	struct obs_output_info output_info = {};
308 	output_info.id = "v4l2sink";
309 	output_info.flags = OBS_OUTPUT_VIDEO;
310 	output_info.get_name = v4l2sink_getname;
311 	output_info.create = v4l2sink_create;
312 	output_info.destroy = v4l2sink_destroy;
313 	output_info.start = v4l2sink_start;
314 	output_info.stop = v4l2sink_stop;
315 	output_info.raw_video = v4l2sink_videotick;
316 	output_info.get_properties = v4l2sink_getproperties;
317 	return output_info;
318 }
319 
320 OBS_DECLARE_MODULE()
321 OBS_MODULE_USE_DEFAULT_LOCALE("v4l2sink", "en-US")
322 
obs_module_load(void)323 bool obs_module_load(void)
324 {
325 	obs_output_info v4l2sink_info = create_output_info();
326 	obs_register_output(&v4l2sink_info);
327 	obs_data_t *settings = obs_data_create();
328 	v4l2_out = obs_output_create("v4l2sink", "V4l2sink",settings, NULL);
329 	obs_data_release(settings);
330 	v4l2sink_signal_init("void v4l2close(string msg, bool opening)");
331 
332 	QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window();
333 	QAction *action = (QAction*)obs_frontend_add_tools_menu_qaction(
334 		obs_module_text("V4l2sink"));
335 
336 	obs_frontend_push_ui_translation(obs_module_get_string);
337 	prop = new V4l2sinkProperties(main_window);
338 	obs_frontend_pop_ui_translation();
339 
340 	auto menu_cb = []
341 	{
342 		prop->setVisible(!prop->isVisible());
343 	};
344 
345 	action->connect(action, &QAction::triggered, menu_cb);
346 
347     	return true;
348 }
349 
obs_module_unload()350 void obs_module_unload()
351 {
352 }
353 
v4l2sink_release()354 void v4l2sink_release()
355 {
356 	obs_output_stop(v4l2_out);
357 	obs_output_release(v4l2_out);
358 }
359 
v4l2sink_enable(const char * dev_name,const char * format)360 void v4l2sink_enable(const char *dev_name, const char *format)
361 {
362 	obs_data_t *settings = obs_output_get_settings(v4l2_out);
363 	obs_data_set_string(settings, "device_name", dev_name);
364 	obs_data_set_string(settings, "format", format);
365 	obs_output_update(v4l2_out,settings);
366 	obs_data_release(settings);
367 	obs_output_start(v4l2_out);
368 }
369 
v4l2sink_disable()370 void v4l2sink_disable()
371 {
372 	obs_output_stop(v4l2_out);
373 }
374 
v4l2sink_get_signal_handler()375 signal_handler_t* v4l2sink_get_signal_handler()
376 {
377 	return obs_output_get_signal_handler(v4l2_out);
378 }
379