1 /*****************************************************************
2 * gmerlin - a general purpose multimedia framework and applications
3 *
4 * Copyright (c) 2001 - 2011 Members of the Gmerlin project
5 * gmerlin-general@lists.sourceforge.net
6 * http://gmerlin.sourceforge.net
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 * *****************************************************************/
21
22 #include <sys/mman.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <sys/ioctl.h>
26 #include <fcntl.h>
27
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <errno.h>
32
33 #include <config.h>
34 #include <gmerlin/translation.h>
35 #include <gmerlin/plugin.h>
36
37 #include <linux/videodev.h>
38
39 #include <unistd.h>
40 #include <gmerlin/utils.h>
41
42 #include <gmerlin/log.h>
43 #define LOG_DOMAIN "i_v4l"
44
45 #include "pwc.h"
46
47 /* Debugging stuff */
48 #if 0
49
50 const struct
51 {
52 int id;
53 char const * const name;
54 }
55 palette_names[] =
56 {
57 { VIDEO_PALETTE_GREY, "Linear greyscale" },
58 { VIDEO_PALETTE_HI240, "High 240 cube (BT848)" },
59 { VIDEO_PALETTE_RGB565, "565 16 bit RGB" },
60 { VIDEO_PALETTE_RGB24, "24bit RGB" },
61 { VIDEO_PALETTE_RGB32, "32bit RGB" },
62 { VIDEO_PALETTE_RGB555, "555 15bit RGB" },
63 { VIDEO_PALETTE_YUV422, "YUV422 capture" },
64 { VIDEO_PALETTE_YUYV, "YUYV" },
65 { VIDEO_PALETTE_UYVY, "UYVY" },
66 { VIDEO_PALETTE_YUV420, "YUV 420" },
67 { VIDEO_PALETTE_YUV411, "YUV 411" },
68 { VIDEO_PALETTE_RAW, "RAW capture (BT848)" },
69 { VIDEO_PALETTE_YUV422P, "YUV 4:2:2 Planar" },
70 { VIDEO_PALETTE_YUV411P, "YUV 4:1:1 Planar" },
71 { VIDEO_PALETTE_YUV420P, "YUV 4:2:0 Planar" },
72 { VIDEO_PALETTE_YUV410P, "YUV 4:1:0 Planar" },
73 };
74
75 static void dump_palette(int palette)
76 {
77 int i;
78 FILE * out = stderr;
79 for(i = 0; i < sizeof(palette_names)/sizeof(palette_names[0]); i++)
80 {
81 if(palette == palette_names[i].id)
82 {
83 fprintf(out, "%s", palette_names[i].name);
84 break;
85 }
86 }
87 }
88
89 static void dump_video_picture(struct video_picture * p)
90 {
91 FILE * out = stderr;
92 fprintf(out , "Video picture:\n");
93 fprintf(out , " Brightness: %d\n", p->brightness);
94 fprintf(out , " Hue: %d\n", p->hue);
95 fprintf(out , " Colour: %d\n", p->colour);
96 fprintf(out , " Contrast: %d\n", p->contrast);
97 fprintf(out , " Whiteness: %d\n", p->whiteness);
98 fprintf(out , " Depth: %d\n", p->depth);
99 fprintf(out , " Palette: ");
100 dump_palette(p->palette);
101 fprintf(out , "\n");
102 }
103 #endif
104
105 /* Colorspace translation stuff */
106
107 static const struct
108 {
109 int v4l;
110 gavl_pixelformat_t gavl;
111 int depth;
112 }
113 pixelformats[] =
114 {
115 { VIDEO_PALETTE_GREY, GAVL_GRAY_8, 8 }, /* Linear greyscale */
116 // VIDEO_PALETTE_HI240 2 /* High 240 cube (BT848) */
117 { VIDEO_PALETTE_RGB565, GAVL_RGB_16, 16 }, /* 565 16 bit RGB */
118 { VIDEO_PALETTE_RGB24, GAVL_BGR_24, 24 }, /* 24bit RGB */
119 { VIDEO_PALETTE_RGB32, GAVL_RGB_32, 32 }, /* 32bit RGB */
120 { VIDEO_PALETTE_RGB555, GAVL_RGB_15, 15 }, /* 555 15bit RGB */
121 { VIDEO_PALETTE_YUV422, GAVL_YUY2, 24 }, /* YUV422 capture */
122 { VIDEO_PALETTE_YUYV, GAVL_YUY2, 24 },
123 { VIDEO_PALETTE_UYVY, GAVL_UYVY, 24 }, /* The great thing about standards is ... */
124 // VIDEO_PALETTE_YUV420 10
125 // VIDEO_PALETTE_YUV411 11 /* YUV411 capture */
126 // VIDEO_PALETTE_RAW 12 /* RAW capture (BT848) */
127 { VIDEO_PALETTE_YUV422P, GAVL_YUV_422_P, 24 }, /* YUV 4:2:2 Planar */
128 { VIDEO_PALETTE_YUV411P, GAVL_YUV_411_P, 24 }, /* YUV 4:1:1 Planar */
129 // { VIDEO_PALETTE_YUV420P, GAVL_YUV_420_P, 12 }, /* YUV 4:2:0 Planar */
130 { VIDEO_PALETTE_YUV420P, GAVL_YUV_420_P, 24 }, /* YUV 4:2:0 Planar */
131
132 // VIDEO_PALETTE_YUV410P 16 /* YUV 4:1:0 Planar */
133
134 };
135
136 static const int
137 num_pixelformats = sizeof(pixelformats)/sizeof(pixelformats[0]);
138
get_gavl_pixelformat(int csp)139 static gavl_pixelformat_t get_gavl_pixelformat(int csp)
140 {
141 int i;
142 for(i = 0; i < num_pixelformats; i++)
143 {
144 if(pixelformats[i].v4l == csp)
145 return pixelformats[i].gavl;
146 }
147 return GAVL_PIXELFORMAT_NONE;
148 }
149
get_v4l_pixelformat(gavl_pixelformat_t csp)150 static int get_v4l_pixelformat(gavl_pixelformat_t csp)
151 {
152 int i;
153 for(i = 0; i < num_pixelformats; i++)
154 {
155 if(pixelformats[i].gavl == csp)
156 return pixelformats[i].v4l;
157 }
158 return -1;
159 }
160
161 /* Input module */
162
163 typedef struct
164 {
165 bg_parameter_info_t * parameters;
166
167 int have_pwc; /* True is we have a Phillips webcam */
168 int have_pwc_parameters; /* True is we have created parameters for
169 a Phillips webcam */
170
171 gavl_video_format_t format;
172
173 void * pwc_priv;
174
175 int fd;
176
177 struct video_picture pic, cfg_pic;
178 struct video_window win, cfg_win;
179
180 int user_width;
181 int user_height;
182 int user_resolution;
183
184 struct video_mbuf mbuf;
185
186 struct video_mmap mmap_struct;
187
188 int frame_index;
189 uint8_t * mmap_buf;
190
191 gavl_video_frame_t * frame;
192 char * device;
193 } v4l_t;
194
open_v4l(void * priv,gavl_audio_format_t * audio_format,gavl_video_format_t * format)195 static int open_v4l(void * priv,
196 gavl_audio_format_t * audio_format,
197 gavl_video_format_t * format)
198 {
199 v4l_t * v4l;
200 v4l = priv;
201
202 /* Open device */
203
204 if((v4l->fd = open(v4l->device, O_RDWR, 0)) < 0)
205 {
206 bg_log(BG_LOG_ERROR, LOG_DOMAIN, "Cannot open device %s: %s",
207 v4l->device, strerror(errno));
208 goto fail;
209 }
210 /* Check if we have a Phillips webcam */
211
212 v4l->have_pwc = bg_pwc_probe(v4l->fd);
213
214 if(v4l->have_pwc)
215 bg_log(BG_LOG_DEBUG, LOG_DOMAIN, "Phillips webcam detected");
216
217 /* Set Picture */
218
219 if(ioctl(v4l->fd, VIDIOCGPICT, &v4l->pic))
220 {
221 bg_log(BG_LOG_ERROR, LOG_DOMAIN, "VIDIOCGPICT failed: %s",
222 strerror(errno));
223 goto fail;
224 }
225 format->pixelformat = get_gavl_pixelformat(v4l->pic.palette);
226 /* If we have a nonsupported pixelformat we try YUV420 */
227
228 if(format->pixelformat == GAVL_PIXELFORMAT_NONE)
229 v4l->pic.palette = get_v4l_pixelformat(GAVL_YUV_420_P);
230
231 /* Transfer values */
232
233 #if 1
234 v4l->pic.brightness = v4l->cfg_pic.brightness;
235 v4l->pic.hue = v4l->cfg_pic.hue;
236 v4l->pic.colour = v4l->cfg_pic.colour;
237 v4l->pic.contrast = v4l->cfg_pic.contrast;
238 v4l->pic.whiteness = v4l->cfg_pic.whiteness; /* Black and white only */
239 #endif
240
241 // dump_video_picture(&v4l->pic);
242
243 if(ioctl(v4l->fd, VIDIOCSPICT, &v4l->pic))
244 {
245 bg_log(BG_LOG_ERROR, LOG_DOMAIN, "VIDIOCSPICT failed: %s",
246 strerror(errno));
247 goto fail;
248 }
249 format->pixelformat = get_gavl_pixelformat(v4l->pic.palette);
250
251 /* Set window */
252
253 if(ioctl(v4l->fd, VIDIOCGWIN, &v4l->win))
254 {
255 bg_log(BG_LOG_ERROR, LOG_DOMAIN, "VIDIOCGWIN failed: %s",
256 strerror(errno));
257 goto fail;
258 }
259 v4l->win.x = 0;
260 v4l->win.y = 0;
261
262 v4l->win.width = v4l->cfg_win.width;
263 v4l->win.height = v4l->cfg_win.height;
264 v4l->win.flags = 0;
265
266 if(ioctl(v4l->fd, VIDIOCSWIN, &v4l->win))
267 {
268 bg_log(BG_LOG_ERROR, LOG_DOMAIN, "VIDIOCSWIN failed: %s (invalaid picture dimensions?)",
269 strerror(errno));
270 goto fail;
271 }
272 /* Setup format */
273
274 format->image_width = v4l->win.width;
275 format->image_height = v4l->win.height;
276
277 format->frame_width = v4l->win.width;
278 format->frame_height = v4l->win.height;
279
280 format->pixel_width = 1;
281 format->pixel_height = 1;
282
283 format->framerate_mode = GAVL_FRAMERATE_VARIABLE;
284 format->frame_duration = 0;
285 format->timescale = 1;
286
287 gavl_video_format_copy(&v4l->format, format);
288
289
290 /* Setup frame */
291
292 gavl_video_frame_set_strides(v4l->frame, format);
293
294 /* Setup mmap */
295
296 if(ioctl(v4l->fd, VIDIOCGMBUF, &v4l->mbuf))
297 {
298 bg_log(BG_LOG_ERROR, LOG_DOMAIN, "VIDIOCGMBUF failed: %s",
299 strerror(errno));
300 goto fail;
301 }
302 v4l->mmap_buf = (uint8_t*)(mmap(0, v4l->mbuf.size, PROT_READ|PROT_WRITE,
303 MAP_SHARED,v4l->fd,0));
304 if((unsigned char*)-1 == v4l->mmap_buf)
305 {
306 bg_log(BG_LOG_ERROR, LOG_DOMAIN, "mmap failed: %s", strerror(errno));
307 goto fail;
308 }
309 v4l->mmap_struct.width = format->image_width;
310 v4l->mmap_struct.height = format->image_height;
311 v4l->mmap_struct.format = v4l->pic.palette;
312
313 v4l->frame_index = 0;
314
315 gavl_video_format_copy(&v4l->format, format);
316
317 return 1;
318 fail:
319 if(v4l->fd >= 0)
320 {
321 close(v4l->fd);
322 v4l->fd = -1;
323 }
324 return 0;
325
326 }
327
close_v4l(void * priv)328 static void close_v4l(void * priv)
329 {
330 v4l_t * v4l;
331 v4l = priv;
332
333 if(v4l->fd >= 0)
334 {
335 close(v4l->fd);
336 if(v4l->mmap_buf)
337 munmap(v4l->mmap_buf, v4l->mbuf.size);
338 }
339 v4l->fd = -1;
340 }
341
read_frame_v4l(void * priv,gavl_video_frame_t * frame,int stream)342 static int read_frame_v4l(void * priv, gavl_video_frame_t * frame, int stream)
343 {
344 v4l_t * v4l;
345 v4l = priv;
346
347 v4l->mmap_struct.frame = v4l->frame_index;
348
349 if(ioctl(v4l->fd, VIDIOCMCAPTURE, &v4l->mmap_struct))
350 return 0;
351
352 if(ioctl(v4l->fd,VIDIOCSYNC, &v4l->frame_index))
353 return 0;
354
355 gavl_video_frame_set_planes(v4l->frame,
356 &v4l->format,
357 &v4l->mmap_buf[v4l->mbuf.offsets[v4l->frame_index]]);
358
359 gavl_video_frame_copy(&v4l->format, frame, v4l->frame);
360 // if(ioctl(v4l->fd,VIDIOCSYNC, &v4l->mmap[v4l->frame_index]))
361
362 v4l->frame_index++;
363 if(v4l->frame_index >= v4l->mbuf.frames)
364 v4l->frame_index = 0;
365
366 return 1;
367 }
368
create_v4l()369 static void * create_v4l()
370 {
371 v4l_t * v4l;
372
373 v4l = calloc(1, sizeof(*v4l));
374
375 v4l->frame = gavl_video_frame_create(NULL);
376 v4l->fd = -1;
377 return v4l;
378 }
379
destroy_v4l(void * priv)380 static void destroy_v4l(void * priv)
381 {
382 v4l_t * v4l;
383 v4l = priv;
384 gavl_video_frame_null(v4l->frame);
385 gavl_video_frame_destroy(v4l->frame);
386 close_v4l(priv);
387
388 if(v4l->parameters)
389 bg_parameter_info_destroy_array(v4l->parameters);
390
391 if(v4l->pwc_priv)
392 {
393 bg_pwc_destroy(v4l->pwc_priv);
394 v4l->pwc_priv = NULL;
395 }
396 free(v4l);
397
398 }
399
400 /* Configuration stuff */
401
402 static const bg_parameter_info_t parameters[] =
403 {
404 {
405 .name = "device_section",
406 .long_name = TRS("Device"),
407 .type = BG_PARAMETER_SECTION
408 },
409 {
410 .name = "device",
411 .long_name = TRS("V4L Device"),
412 .type = BG_PARAMETER_DEVICE,
413 .val_default = { .val_str = "/dev/video0" },
414 },
415 {
416 .name = "res",
417 .long_name = TRS("Resolution"),
418 .type = BG_PARAMETER_SECTION,
419 },
420 {
421 .name = "resolution",
422 .long_name = TRS("Resolution"),
423 .type = BG_PARAMETER_STRINGLIST,
424 .val_default = { .val_str = "QVGA (320x240)" },
425 .multi_names = (char const *[]){ "QSIF (160x112)",
426 "QCIF (176x144)",
427 "QVGA (320x240)",
428 "SIF(352x240)",
429 "CIF (352x288)",
430 "VGA (640x480)",
431 "User defined",
432 NULL },
433 .multi_labels = (char const *[]){ TRS("QSIF (160x112)"),
434 TRS("QCIF (176x144)"),
435 TRS("QVGA (320x240)"),
436 TRS("SIF(352x240)"),
437 TRS("CIF (352x288)"),
438 TRS("VGA (640x480)"),
439 TRS("User defined"),
440 NULL },
441 },
442 {
443 .name = "user_width",
444 .long_name = TRS("User defined width"),
445 .type = BG_PARAMETER_INT,
446 .val_default = { .val_i = 720 },
447 .val_min = { .val_i = 160 },
448 .val_max = { .val_i = 1024 },
449 },
450 {
451 .name = "user_height",
452 .long_name = TRS("User defined height"),
453 .type = BG_PARAMETER_INT,
454 .val_default = { .val_i = 576 },
455 .val_min = { .val_i = 112 },
456 .val_max = { .val_i = 768 },
457 },
458 {
459 .name = "settings",
460 .long_name = TRS("Settings"),
461 .type = BG_PARAMETER_SECTION,
462 },
463 {
464 .name = "brightness",
465 .long_name = TRS("Brightness"),
466 .type = BG_PARAMETER_SLIDER_INT,
467 .flags = BG_PARAMETER_SYNC,
468 .val_min = { .val_i = 0 },
469 .val_max = { .val_i = 65535 },
470 .val_default = { .val_i = 40000 },
471 },
472 {
473 .name = "hue",
474 .long_name = TRS("Hue"),
475 .type = BG_PARAMETER_SLIDER_INT,
476 .flags = BG_PARAMETER_SYNC,
477 .val_min = { .val_i = 0 },
478 .val_max = { .val_i = 65535 },
479 .val_default = { .val_i = 65535 },
480 },
481 {
482 .name = "colour",
483 .long_name = TRS("Colour"),
484 .type = BG_PARAMETER_SLIDER_INT,
485 .flags = BG_PARAMETER_SYNC,
486 .val_min = { .val_i = 0 },
487 .val_max = { .val_i = 65535 },
488 .val_default = { .val_i = 30000 },
489 },
490 {
491 .name = "contrast",
492 .long_name = TRS("Contrast"),
493 .type = BG_PARAMETER_SLIDER_INT,
494 .flags = BG_PARAMETER_SYNC,
495 .val_min = { .val_i = 0 },
496 .val_max = { .val_i = 65535 },
497 .val_default = { .val_i = 30000 },
498 },
499 {
500 .name = "whiteness",
501 .long_name = TRS("Whiteness"),
502 .type = BG_PARAMETER_SLIDER_INT,
503 .flags = BG_PARAMETER_SYNC,
504 .val_min = { .val_i = 0 },
505 .val_max = { .val_i = 65535 },
506 .val_default = { .val_i = 30000 },
507 },
508 { /* End of parameters */ }
509 };
510
create_parameters(v4l_t * v4l)511 static void create_parameters(v4l_t * v4l)
512 {
513 if(v4l->parameters)
514 bg_parameter_info_destroy_array(v4l->parameters);
515 v4l->parameters = bg_parameter_info_copy_array(parameters);
516
517 #if 0
518 if((v4l->fd < 0) &&
519 ((v4l->fd = open(v4l->device, O_RDWR, 0)) >= 0))
520 {
521 v4l->have_pwc = bg_pwc_probe(v4l->fd);
522 close(v4l->fd);
523 v4l->fd = -1;
524 }
525 #endif
526
527 if(v4l->have_pwc)
528 {
529 v4l->pwc_priv = bg_pwc_get_parameters(v4l->fd, &v4l->parameters);
530 v4l->have_pwc_parameters = 1;
531 }
532 }
533
get_parameters_v4l(void * priv)534 static const bg_parameter_info_t * get_parameters_v4l(void * priv)
535 {
536 v4l_t * v4l;
537 v4l = priv;
538
539 if(!v4l->parameters ||
540 (v4l->have_pwc != v4l->have_pwc_parameters))
541 {
542 create_parameters(v4l);
543 }
544
545 return v4l->parameters;
546 }
547
set_parameter_v4l(void * priv,const char * name,const bg_parameter_value_t * val)548 static void set_parameter_v4l(void * priv, const char * name,
549 const bg_parameter_value_t * val)
550 {
551 char * pos;
552 v4l_t * v4l;
553 v4l = priv;
554
555 if(!name)
556 {
557 if(v4l->user_resolution)
558 {
559 v4l->cfg_win.width = v4l->user_width;
560 v4l->cfg_win.height = v4l->user_height;
561 }
562 bg_pwc_set_parameter(v4l->fd, v4l->pwc_priv, NULL, val);
563 return;
564 }
565
566 if(!strncmp(name, "pwc_", 4))
567 {
568 bg_pwc_set_parameter(v4l->fd, v4l->pwc_priv, name, val);
569 }
570 else if(!strcmp(name, "resolution"))
571 {
572 if(!strcmp(val->val_str, "QSIF (160x112)"))
573 {
574 v4l->cfg_win.width = 160;
575 v4l->cfg_win.height = 112;
576 v4l->user_resolution = 0;
577 }
578 else if(!strcmp(val->val_str, "QCIF (176x144)"))
579 {
580 v4l->cfg_win.width = 176;
581 v4l->cfg_win.height = 144;
582 v4l->user_resolution = 0;
583 }
584 else if(!strcmp(val->val_str, "QVGA (320x240)"))
585 {
586 v4l->cfg_win.width = 320;
587 v4l->cfg_win.height = 240;
588 v4l->user_resolution = 0;
589 }
590 else if(!strcmp(val->val_str, "SIF(352x240)"))
591 {
592 v4l->cfg_win.width = 352;
593 v4l->cfg_win.height = 240;
594 v4l->user_resolution = 0;
595 }
596 else if(!strcmp(val->val_str, "CIF (352x288)"))
597 {
598 v4l->cfg_win.width = 352;
599 v4l->cfg_win.height = 288;
600 v4l->user_resolution = 0;
601 }
602 else if(!strcmp(val->val_str, "VGA (640x480)"))
603 {
604 v4l->cfg_win.width = 640;
605 v4l->cfg_win.height = 480;
606 v4l->user_resolution = 0;
607 }
608 else if(!strcmp(val->val_str, "User defined"))
609 {
610 v4l->user_resolution = 1;
611 }
612 }
613 else if(!strcmp(name, "device"))
614 {
615 v4l->device = bg_strdup(v4l->device, val->val_str);
616 pos = strchr(v4l->device, ' '); if(pos) *pos = '\0';
617 }
618 else if(!strcmp(name, "user_width"))
619 {
620 v4l->user_width = val->val_i;
621 }
622 else if(!strcmp(name, "user_height"))
623 {
624 v4l->user_height = val->val_i;
625 }
626 else if(!strcmp(name, "brightness"))
627 {
628 v4l->cfg_pic.brightness = val->val_i;
629
630 if(v4l->fd > 0)
631 {
632 if(ioctl(v4l->fd, VIDIOCGPICT, &v4l->pic))
633 return;
634 v4l->pic.brightness = val->val_i;
635 if(ioctl(v4l->fd, VIDIOCSPICT, &v4l->pic))
636 return;
637 }
638 }
639 else if(!strcmp(name, "hue"))
640 {
641 v4l->cfg_pic.hue = val->val_i;
642 if(v4l->fd > 0)
643 {
644 if(ioctl(v4l->fd, VIDIOCGPICT, &v4l->pic))
645 return;
646 v4l->pic.hue = val->val_i;
647 if(ioctl(v4l->fd, VIDIOCSPICT, &v4l->pic))
648 return;
649 }
650 }
651 else if(!strcmp(name, "colour"))
652 {
653 v4l->cfg_pic.colour = val->val_i;
654 if(v4l->fd > 0)
655 {
656 if(ioctl(v4l->fd, VIDIOCGPICT, &v4l->pic))
657 return;
658 v4l->pic.colour = val->val_i;
659 if(ioctl(v4l->fd, VIDIOCSPICT, &v4l->pic))
660 return;
661 }
662 }
663 else if(!strcmp(name, "contrast"))
664 {
665 v4l->cfg_pic.contrast = val->val_i;
666 if(v4l->fd > 0)
667 {
668 if(ioctl(v4l->fd, VIDIOCGPICT, &v4l->pic))
669 return;
670 v4l->pic.contrast = val->val_i;
671 if(ioctl(v4l->fd, VIDIOCSPICT, &v4l->pic))
672 return;
673 }
674
675 }
676 else if(!strcmp(name, "whiteness"))
677 {
678 v4l->cfg_pic.whiteness = val->val_i;
679 if(v4l->fd > 0)
680 {
681 if(ioctl(v4l->fd, VIDIOCGPICT, &v4l->pic))
682 return;
683 v4l->pic.whiteness = val->val_i;
684 if(ioctl(v4l->fd, VIDIOCSPICT, &v4l->pic))
685 return;
686 }
687 }
688 }
689
690
691 const bg_recorder_plugin_t the_plugin =
692 {
693 .common =
694 {
695 BG_LOCALE,
696 .name = "i_v4l",
697 .long_name = TRS("V4L"),
698 .description = TRS("video4linux recording plugin. Supports only video and no tuner decives."),
699 .type = BG_PLUGIN_RECORDER_VIDEO,
700 .flags = BG_PLUGIN_RECORDER,
701 .priority = BG_PLUGIN_PRIORITY_MAX,
702 .create = create_v4l,
703 .destroy = destroy_v4l,
704
705 .get_parameters = get_parameters_v4l,
706 .set_parameter = set_parameter_v4l,
707 },
708
709 .open = open_v4l,
710 .close = close_v4l,
711 .read_video = read_frame_v4l,
712
713 };
714
715 /* Include this into all plugin modules exactly once
716 to let the plugin loader obtain the API version */
717 BG_GET_PLUGIN_API_VERSION;
718