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