1 /*
2 * import_v4lcam.c -- imports video frames from v4l2 using libv4l*
3 * with special focus on webcams.
4 * (C) 2009-2010 Francesco Romani <fromani at gmail dot com>
5 * based on import_v4l2.c code, which is
6 * (C) Erik Slagter <erik@slagter.name> Sept 2003
7 *
8 * This file is part of transcode, a video stream processing tool.
9 *
10 * transcode is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * transcode is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #define MOD_NAME "import_v4lcam.so"
25 #define MOD_VERSION "v0.1.0 (2009-08-30)"
26 #define MOD_CODEC "(video) v4l2"
27
28 #include "src/transcode.h"
29
30
31 static int verbose_flag = TC_QUIET;
32 static int capability_flag = TC_CAP_RGB|TC_CAP_YUV;
33
34 /*
35 * Briefing
36 *
37 * Q: why a new module?
38 * Q: why don't just enhance import_v4l2?
39 * A: because I want take this chance to do a fresh start with a v4l import
40 * module, so we can get rid of some old code, try to redesign/rewrite
41 * it in a better way, experimenting new designes and so on. I want the
42 * freedom to add special code and special design decisions useful for
43 * webcams only (or just mostly). import_v4l2 will stay and the experiments
44 * which time proven to be good will be backported.
45 * Eventually, v4lcam can be merged into the main v4l module.
46 *
47 * Q: there is some duplicate code with import_v4l2.c. Why?
48 * A: because I'm taking advantage of being a separate module, and because
49 * I'm experimenting new stuff. After a while, the remaining duplicated
50 * parts will be merged in a common source.
51 *
52 * Q: why libv4lconvert? We can just extend aclib.
53 * A: no objections of course (but no time either!). However, libv4lconvert
54 * has IMHO a slightly different focus wrt aclib and I think it's just
55 * fine to use both of them. As example, the MJPG->I420 conversion
56 * should NOT enter into aclib (eventually v4lcam can emit MJPG frames
57 * too, when the module pipeline get enhanced enough).
58
59 */
60
61 /*%*
62 *%* DESCRIPTION
63 *%* This module allow to capture video frames through a V4L2 (V4L api version 2)
64 *%* device. This module is specialized for webcam devices.
65 *%*
66 *%* #BUILD-DEPENDS
67 *%*
68 *%* #DEPENDS
69 *%*
70 *%* PROCESSING
71 *%* import/demuxer
72 *%*
73 *%* MEDIA
74 *%* video
75 *%*
76 *%* #INPUT
77 *%*
78 *%* OUTPUT
79 *%* YUV420P, RGB24
80 *%*/
81
82 #define MOD_PRE tc_v4lcam
83 #include "import_def.h"
84
85 #define _ISOC9X_SOURCE 1
86
87 #include <sys/ioctl.h>
88 #include <sys/mman.h>
89
90 #include <linux/types.h>
91
92 // The v4l2_buffer struct check is because some distributions protect that
93 // struct in videodev2 with a #ifdef __KERNEL__ (SuSE 9.0)
94
95 #if defined(HAVE_LINUX_VIDEODEV2_H) && defined(HAVE_STRUCT_V4L2_BUFFER)
96 #define _LINUX_TIME_H
97 #include <linux/videodev2.h>
98 #else
99 #include "videodev2.h"
100 #endif
101
102 #include "libv4l2.h"
103 #include "libv4lconvert.h"
104
105 #include "libtc/libtc.h"
106 #include "libtc/optstr.h"
107
108 #define TC_V4L2_BUFFERS_NUM (32)
109
110 /* TODO: memset() verify and sanitization */
111
112 typedef struct tcv4lbuffer TCV4LBuffer;
113 struct tcv4lbuffer {
114 void *start;
115 size_t length;
116 };
117
118 typedef struct v4l2source_ V4L2Source;
119
120 /* FIXME: naming */
121 typedef int (*TCV4LFetchDataFn)(V4L2Source *vs,
122 uint8_t *src, int src_len,
123 uint8_t *dst, int dst_len);
124
125 struct v4l2source_ {
126 int video_fd;
127 int video_sequence;
128
129 int v4l_dst_csp;
130 struct v4l2_format v4l_dst_fmt;
131 struct v4l2_format v4l_src_fmt;
132 struct v4lconvert_data *v4l_convert;
133 int buffers_count;
134
135 int width;
136 int height;
137
138 TCV4LFetchDataFn fetch_data;
139 TCV4LBuffer buffers[TC_V4L2_BUFFERS_NUM];
140 };
141
tc_v4l2_fetch_data_memcpy(V4L2Source * vs,uint8_t * src,int src_len,uint8_t * dst,int dst_len)142 static int tc_v4l2_fetch_data_memcpy(V4L2Source *vs,
143 uint8_t *src, int src_len,
144 uint8_t *dst, int dst_len)
145 {
146 int ret = TC_ERROR;
147 if (dst_len >= src_len) {
148 ac_memcpy(dst, src, src_len);
149 ret = TC_OK;
150 }
151 return ret;
152 }
153
tc_v4l2_fetch_data_v4lconv(V4L2Source * vs,uint8_t * src,int src_len,uint8_t * dst,int dst_len)154 static int tc_v4l2_fetch_data_v4lconv(V4L2Source *vs,
155 uint8_t *src, int src_len,
156 uint8_t *dst, int dst_len)
157 {
158 int err = v4lconvert_convert(vs->v4l_convert,
159 &(vs->v4l_src_fmt),
160 &(vs->v4l_dst_fmt),
161 src, src_len, dst, dst_len);
162
163 return (err == -1) ?TC_ERROR :TC_OK; /* FIXME */
164 }
165
166 /* FIXME: reorganize the layout */
tc_v4l2_video_grab_frame(V4L2Source * vs,uint8_t * dest,size_t length)167 static int tc_v4l2_video_grab_frame(V4L2Source *vs, uint8_t *dest, size_t length)
168 {
169 static struct v4l2_buffer buffer; /* FIXME */
170 int ix, err = 0, eio = 0, ret = TC_ERROR;
171
172 // get buffer
173 buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
174 buffer.memory = V4L2_MEMORY_MMAP;
175
176 err = v4l2_ioctl(vs->video_fd, VIDIOC_DQBUF, &buffer);
177 if (err < 0) {
178 tc_log_perror(MOD_NAME,
179 "error in setup grab buffer (ioctl(VIDIOC_DQBUF) failed)");
180
181 if (errno != EIO) {
182 return TC_OK;
183 } else {
184 eio = 1;
185
186 for (ix = 0; ix < vs->buffers_count; ix++) {
187 buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
188 buffer.memory = V4L2_MEMORY_MMAP;
189 buffer.index = ix;
190 buffer.flags = 0;
191
192 err = v4l2_ioctl(vs->video_fd, VIDIOC_DQBUF, &buffer);
193 if (err < 0)
194 tc_log_perror(MOD_NAME,
195 "error in recovering grab buffer (ioctl(DQBUF) failed)");
196 }
197
198 for (ix = 0; ix < vs->buffers_count; ix++) {
199 buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
200 buffer.memory = V4L2_MEMORY_MMAP;
201 buffer.index = ix;
202 buffer.flags = 0;
203
204 err = v4l2_ioctl(vs->video_fd, VIDIOC_QBUF, &buffer);
205 if (err < 0)
206 tc_log_perror(MOD_NAME,
207 "error in recovering grab buffer (ioctl(QBUF) failed)");
208 }
209 }
210 }
211
212 ix = buffer.index;
213
214 ret = vs->fetch_data(vs,
215 vs->buffers[ix].start, buffer.bytesused,
216 dest, length);
217
218 // enqueue buffer again
219 if (!eio) {
220 buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
221 buffer.memory = V4L2_MEMORY_MMAP;
222 buffer.flags = 0;
223
224 err = v4l2_ioctl(vs->video_fd, VIDIOC_QBUF, &buffer);
225 if (err < 0) {
226 tc_log_perror(MOD_NAME, "error in enqueuing buffer (ioctl(VIDIOC_QBUF) failed)");
227 return TC_OK;
228 }
229 }
230
231 return ret;
232 }
233
tc_v4l2_video_count_buffers(V4L2Source * vs)234 static int tc_v4l2_video_count_buffers(V4L2Source *vs)
235 {
236 struct v4l2_buffer buffer;
237 int ix, ret, buffers_filled = 0;
238
239 for (ix = 0; ix < vs->buffers_count; ix++) {
240 buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
241 buffer.memory = V4L2_MEMORY_MMAP;
242 buffer.index = ix;
243
244 ret = v4l2_ioctl(vs->video_fd, VIDIOC_QUERYBUF, &buffer);
245 if (ret < 0) {
246 tc_log_perror(MOD_NAME,
247 "error in querying buffers"
248 " (ioctl(VIDIOC_QUERYBUF) failed)");
249 return -1;
250 }
251
252 if (buffer.flags & V4L2_BUF_FLAG_DONE)
253 buffers_filled++;
254 }
255 return buffers_filled;
256 }
257
tc_v4l2_video_check_capabilities(V4L2Source * vs)258 static int tc_v4l2_video_check_capabilities(V4L2Source *vs)
259 {
260 struct v4l2_capability caps;
261 int err = 0;
262
263 err = v4l2_ioctl(vs->video_fd, VIDIOC_QUERYCAP, &caps);
264 if (err < 0) {
265 tc_log_error(MOD_NAME, "driver does not support querying capabilities");
266 return TC_ERROR;
267 }
268
269 if (!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
270 tc_log_error(MOD_NAME, "driver does not support video capture");
271 return TC_ERROR;
272 }
273
274 if (!(caps.capabilities & V4L2_CAP_STREAMING)) {
275 tc_log_error(MOD_NAME, "driver does not support streaming (mmap) video capture");
276 return TC_ERROR;
277 }
278
279 if (verbose_flag > TC_INFO) {
280 tc_log_info(MOD_NAME, "v4l2 video grabbing, driver = %s, device = %s",
281 caps.driver, caps.card);
282 }
283
284 return TC_OK;
285 }
286
287 #define pixfmt_to_fourcc(pixfmt, fcc) do { \
288 fcc[0] = (pixfmt >> 0 ) & 0xFF; \
289 fcc[1] = (pixfmt >> 8 ) & 0xFF; \
290 fcc[2] = (pixfmt >> 16) & 0xFF; \
291 fcc[3] = (pixfmt >> 24) & 0xFF; \
292 } while (0)
293
tc_v4l2_video_setup_image_format(V4L2Source * vs,int width,int height)294 static int tc_v4l2_video_setup_image_format(V4L2Source *vs, int width, int height)
295 {
296 int err = 0;
297
298 vs->width = width;
299 vs->height = height;
300
301 vs->v4l_convert = v4lconvert_create(vs->video_fd);
302 if (!vs->v4l_convert) {
303 return TC_ERROR;
304 }
305
306 memset(&(vs->v4l_dst_fmt), 0, sizeof(vs->v4l_dst_fmt));
307 vs->v4l_dst_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
308 vs->v4l_dst_fmt.fmt.pix.width = width;
309 vs->v4l_dst_fmt.fmt.pix.height = height;
310 vs->v4l_dst_fmt.fmt.pix.pixelformat = vs->v4l_dst_csp;
311
312 err = v4lconvert_try_format(vs->v4l_convert,
313 &(vs->v4l_dst_fmt), &(vs->v4l_src_fmt));
314 if (err) {
315 tc_log_error(MOD_NAME, "unable to match formats: %s",
316 v4lconvert_get_error_message(vs->v4l_convert));
317 return TC_ERROR;
318 }
319
320 err = v4l2_ioctl(vs->video_fd, VIDIOC_S_FMT, &(vs->v4l_src_fmt));
321 if (err < 0) {
322 tc_log_error(MOD_NAME, "error while setting the cam image format");
323 return TC_ERROR;
324 }
325
326 if (!v4lconvert_needs_conversion(vs->v4l_convert,
327 &(vs->v4l_src_fmt),
328 &(vs->v4l_dst_fmt))) {
329 tc_log_info(MOD_NAME, "fetch frames directly");
330 vs->fetch_data = tc_v4l2_fetch_data_memcpy;
331 /* Into the near future we should aim for zero-copy. -- FR */
332 } else {
333 char src_fcc[5] = { '\0' };
334 char dst_fcc[5] = { '\0' };
335
336 pixfmt_to_fourcc(vs->v4l_src_fmt.fmt.pix.pixelformat, src_fcc);
337 pixfmt_to_fourcc(vs->v4l_dst_fmt.fmt.pix.pixelformat, dst_fcc);
338
339 tc_log_info(MOD_NAME, "fetch frames using libv4lconvert "
340 "[%s] -> [%s]",
341 src_fcc, dst_fcc);
342 vs->fetch_data = tc_v4l2_fetch_data_v4lconv;
343 }
344
345 return TC_OK;
346 }
347
tc_v4l2_teardown_image_format(V4L2Source * vs)348 static void tc_v4l2_teardown_image_format(V4L2Source *vs)
349 {
350 if (vs->v4l_convert) {
351 v4lconvert_destroy(vs->v4l_convert);
352 vs->v4l_convert = NULL;
353 }
354 }
355
tc_v4l2_video_setup_stream_parameters(V4L2Source * vs,int fps)356 static int tc_v4l2_video_setup_stream_parameters(V4L2Source *vs, int fps)
357 {
358 struct v4l2_streamparm streamparm;
359 int err = 0;
360
361 memset(&streamparm, 0, sizeof(streamparm));
362 streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
363 streamparm.parm.capture.capturemode = 0;
364 streamparm.parm.capture.timeperframe.numerator = 1e7;
365 streamparm.parm.capture.timeperframe.denominator = fps;
366
367 err = v4l2_ioctl(vs->video_fd, VIDIOC_S_PARM, &streamparm);
368 if (err < 0) {
369 tc_log_warn(MOD_NAME, "driver does not support setting parameters"
370 " (ioctl(VIDIOC_S_PARM) returns \"%s\")",
371 errno <= sys_nerr ? sys_errlist[errno] : "unknown");
372 }
373 return TC_OK;
374 }
375
tc_v4l2_video_get_capture_buffer_count(V4L2Source * vs)376 static int tc_v4l2_video_get_capture_buffer_count(V4L2Source *vs)
377 {
378 struct v4l2_requestbuffers reqbuf;
379 int err = 0;
380
381 reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
382 reqbuf.memory = V4L2_MEMORY_MMAP;
383 reqbuf.count = TC_V4L2_BUFFERS_NUM;
384
385 err = v4l2_ioctl(vs->video_fd, VIDIOC_REQBUFS, &reqbuf);
386 if (err < 0) {
387 tc_log_perror(MOD_NAME, "VIDIOC_REQBUFS");
388 return TC_ERROR;
389 }
390
391 vs->buffers_count = TC_MIN(reqbuf.count, TC_V4L2_BUFFERS_NUM);
392
393 if (vs->buffers_count < 2) {
394 tc_log_error(MOD_NAME, "not enough buffers for capture");
395 return TC_ERROR;
396 }
397
398 if (verbose_flag > TC_INFO) {
399 tc_log_info(MOD_NAME, "%i buffers available (maximum supported: %i)",
400 vs->buffers_count, TC_V4L2_BUFFERS_NUM);
401 }
402 return TC_OK;
403 }
404
405
tc_v4l2_video_setup_capture_buffers(V4L2Source * vs)406 static int tc_v4l2_video_setup_capture_buffers(V4L2Source *vs)
407 {
408 struct v4l2_buffer buffer;
409 int ix, err = 0;
410
411 /* map the buffers */
412 for (ix = 0; ix < vs->buffers_count; ix++) {
413 buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
414 buffer.memory = V4L2_MEMORY_MMAP;
415 buffer.index = ix;
416
417 err = v4l2_ioctl(vs->video_fd, VIDIOC_QUERYBUF, &buffer);
418 if (err < 0) {
419 tc_log_perror(MOD_NAME, "VIDIOC_QUERYBUF");
420 return TC_ERROR;
421 }
422
423 vs->buffers[ix].length = buffer.length;
424 vs->buffers[ix].start = v4l2_mmap(0, buffer.length,
425 PROT_READ|PROT_WRITE, MAP_SHARED,
426 vs->video_fd, buffer.m.offset);
427
428 if (vs->buffers[ix].start == MAP_FAILED) {
429 tc_log_perror(MOD_NAME, "mmap");
430 return TC_ERROR;
431 }
432 }
433
434 /* then enqueue them all */
435 for (ix = 0; ix < vs->buffers_count; ix++) {
436 buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
437 buffer.memory = V4L2_MEMORY_MMAP;
438 buffer.index = ix;
439
440 err = v4l2_ioctl(vs->video_fd, VIDIOC_QBUF, &buffer);
441 if (err < 0) {
442 tc_log_perror(MOD_NAME, "VIDIOC_QBUF");
443 return TC_ERROR;
444 }
445 }
446
447 return TC_OK;
448 }
449
tc_v4l2_capture_start(V4L2Source * vs)450 static int tc_v4l2_capture_start(V4L2Source *vs)
451 {
452 int err = 0, arg = V4L2_BUF_TYPE_VIDEO_CAPTURE;
453
454 err = v4l2_ioctl(vs->video_fd, VIDIOC_STREAMON, &arg);
455 if (err < 0) {
456 /* ugh, needs VIDEO_CAPTURE */
457 tc_log_perror(MOD_NAME, "VIDIOC_STREAMON");
458 return TC_ERROR;
459 }
460
461 return TC_OK;
462 }
463
tc_v4l2_capture_stop(V4L2Source * vs)464 static int tc_v4l2_capture_stop(V4L2Source *vs)
465 {
466 int err = 0, arg = V4L2_BUF_TYPE_VIDEO_CAPTURE;
467
468 err = v4l2_ioctl(vs->video_fd, VIDIOC_STREAMOFF, &arg);
469 if (err < 0) {
470 /* ugh, needs VIDEO_CAPTURE */
471 tc_log_perror(MOD_NAME, "VIDIOC_STREAMOFF");
472 return TC_ERROR;
473 }
474
475 return TC_OK;
476 }
477
tc_v4l2_parse_options(V4L2Source * vs,int layout,const char * options)478 static int tc_v4l2_parse_options(V4L2Source *vs, int layout, const char *options)
479 {
480 switch (layout) {
481 case CODEC_RGB:
482 case TC_CODEC_RGB:
483 vs->v4l_dst_csp = V4L2_PIX_FMT_RGB24;
484 break;
485 case CODEC_YUV:
486 case TC_CODEC_YUV420P:
487 vs->v4l_dst_csp = V4L2_PIX_FMT_YUV420;
488 break;
489 case CODEC_YUV422:
490 case TC_CODEC_YUV422P:
491 vs->v4l_dst_csp = V4L2_PIX_FMT_YYUV;
492 break;
493 default:
494 tc_log_error(MOD_NAME,
495 "colorspace (0x%X) must be one of"
496 " RGB24, YUV 4:2:0 or YUV 4:2:2",
497 layout);
498 return TC_ERROR;
499 }
500
501 return TC_OK;
502 }
503
504 /* ============================================================
505 * V4L2 CORE
506 * ============================================================*/
507
508 #define RETURN_IF_FAILED(RET) do { \
509 if ((RET) != TC_OK) { \
510 return (RET); \
511 } \
512 } while (0)
513
tc_v4l2_video_init(V4L2Source * vs,int layout,const char * device,int width,int height,int fps,const char * options)514 static int tc_v4l2_video_init(V4L2Source *vs,
515 int layout, const char *device,
516 int width, int height, int fps,
517 const char *options)
518 {
519 int ret = tc_v4l2_parse_options(vs, layout, options);
520 RETURN_IF_FAILED(ret);
521
522 vs->video_fd = v4l2_open(device, O_RDWR, 0);
523 if (vs->video_fd < 0) {
524 tc_log_error(MOD_NAME, "cannot open video device %s", device);
525 return TC_ERROR;
526 }
527
528 ret = tc_v4l2_video_check_capabilities(vs);
529 RETURN_IF_FAILED(ret);
530
531 ret = tc_v4l2_video_setup_image_format(vs, width, height);
532 RETURN_IF_FAILED(ret);
533
534 ret = tc_v4l2_video_setup_stream_parameters(vs, fps);
535 RETURN_IF_FAILED(ret);
536
537 ret = tc_v4l2_video_get_capture_buffer_count(vs);
538 RETURN_IF_FAILED(ret);
539
540 ret = tc_v4l2_video_setup_capture_buffers(vs);
541 RETURN_IF_FAILED(ret);
542
543 return tc_v4l2_capture_start(vs);
544 }
545
tc_v4l2_video_get_frame(V4L2Source * vs,uint8_t * data,size_t size)546 static int tc_v4l2_video_get_frame(V4L2Source *vs, uint8_t *data, size_t size)
547 {
548 int ret;
549 int buffers_filled = tc_v4l2_video_count_buffers(vs);
550
551 if (buffers_filled == -1) {
552 tc_log_warn(MOD_NAME, "unable to get the capture buffers count,"
553 " assuming OK");
554 buffers_filled = 0;
555 }
556
557 if (buffers_filled > (vs->buffers_count * 3 / 4)) {
558 tc_log_error(MOD_NAME, "running out of capture buffers (%d left from %d total), "
559 "stopping capture",
560 vs->buffers_count - buffers_filled,
561 vs->buffers_count);
562
563 ret = tc_v4l2_capture_stop(vs);
564 } else {
565 ret = tc_v4l2_video_grab_frame(vs, data, size);
566 vs->video_sequence++;
567 }
568
569 return ret;
570 }
571
tc_v4l2_video_grab_stop(V4L2Source * vs)572 static int tc_v4l2_video_grab_stop(V4L2Source *vs)
573 {
574 int ix, ret;
575
576 tc_v4l2_teardown_image_format(vs);
577
578 ret = tc_v4l2_capture_stop(vs);
579 RETURN_IF_FAILED(ret);
580
581 for (ix = 0; ix < vs->buffers_count; ix++)
582 v4l2_munmap(vs->buffers[ix].start, vs->buffers[ix].length);
583
584 v4l2_close(vs->video_fd);
585 vs->video_fd = -1;
586
587 return TC_OK;
588 }
589
590 /* ============================================================
591 * TRANSCODE INTERFACE
592 * ============================================================*/
593
594 static V4L2Source VS;
595
596 /* ------------------------------------------------------------
597 * open stream
598 * ------------------------------------------------------------*/
599
600 MOD_open
601 {
602 if (param->flag == TC_VIDEO) {
603 if (tc_v4l2_video_init(&VS,
604 vob->im_v_codec, vob->video_in_file,
605 vob->im_v_width, vob->im_v_height,
606 vob->fps, vob->im_v_string)) {
607 return TC_ERROR;
608 }
609 } else {
610 tc_log_error(MOD_NAME, "unsupported request (init)");
611 return TC_ERROR;
612 }
613
614 return TC_OK;
615 }
616
617 /* ------------------------------------------------------------
618 * decode stream
619 * ------------------------------------------------------------*/
620
621 MOD_decode
622 {
623 if (param->flag == TC_VIDEO) {
624 if (tc_v4l2_video_get_frame(&VS, param->buffer, param->size)) {
625 tc_log_error(MOD_NAME, "error in grabbing video");
626 return TC_ERROR;
627 }
628 } else {
629 tc_log_error(MOD_NAME, "unsupported request (decode)");
630 return TC_ERROR;
631 }
632
633 return TC_OK;
634 }
635
636 /* ------------------------------------------------------------
637 * close stream
638 * ------------------------------------------------------------*/
639
640 MOD_close
641 {
642 if (param->flag == TC_VIDEO) {
643 tc_v4l2_video_grab_stop(&VS);
644 } else {
645 tc_log_error(MOD_NAME, "unsupported request (close)");
646 return TC_ERROR;
647 }
648
649 return TC_OK;
650 }
651
652 /*************************************************************************/
653
654 /*
655 * Local variables:
656 * c-file-style: "stroustrup"
657 * c-file-offsets: ((case-label . *) (statement-case-intro . *))
658 * indent-tabs-mode: nil
659 * End:
660 *
661 * vim: expandtab shiftwidth=4:
662 */
663
664