1 /* fswebcam - Small and simple webcam for *nix */
2 /*===========================================================*/
3 /* Copyright (C)2005-2009 Philip Heron <phil@firestorm.cx> */
5 /* This program is distributed under the terms of the GNU */
6 /* General Public License, version 2. You may use, modify, */
7 /* and redistribute it under the terms of this license. A */
8 /* copy should be included with this source. */
23 #include <sys/types.h>
30 #define CLIP(val, min, max) (val > max) ? max : ((val < min) ? min : val)
32 #define ALIGN_LEFT (0)
33 #define ALIGN_CENTER (1)
34 #define ALIGN_RIGHT (2)
37 #define TOP_BANNER (1)
38 #define BOTTOM_BANNER (2)
40 #define FORMAT_JPEG (0)
41 #define FORMAT_PNG (1)
44 #define OPT_VERSION (OPTBASE + 0)
45 #define OPT_PID (OPTBASE + 1)
46 #define OPT_OFFSET (OPTBASE + 2)
47 #define OPT_LIST_INPUTS (OPTBASE + 3)
48 #define OPT_LIST_TUNERS (OPTBASE + 4)
49 #define OPT_LIST_FORMATS (OPTBASE + 5)
50 #define OPT_LIST_CONTROLS (OPTBASE + 6)
51 #define OPT_LIST_FRAMESIZES (OPTBASE + 7)
52 #define OPT_LIST_FRAMERATES (OPTBASE + 8)
53 #define OPT_BRIGHTNESS (OPTBASE + 9)
54 #define OPT_HUE (OPTBASE + 10)
55 #define OPT_COLOUR (OPTBASE + 11)
56 #define OPT_CONTRAST (OPTBASE + 12)
57 #define OPT_WHITENESS (OPTBASE + 13)
58 #define OPT_REVERT (OPTBASE + 14)
59 #define OPT_FLIP (OPTBASE + 15)
60 #define OPT_CROP (OPTBASE + 16)
61 #define OPT_SCALE (OPTBASE + 17)
62 #define OPT_ROTATE (OPTBASE + 18)
63 #define OPT_DEINTERLACE (OPTBASE + 19)
64 #define OPT_INVERT (OPTBASE + 20)
65 #define OPT_GREYSCALE (OPTBASE + 21)
66 #define OPT_NO_BANNER (OPTBASE + 22)
67 #define OPT_TOP_BANNER (OPTBASE + 23)
68 #define OPT_BOTTOM_BANNER (OPTBASE + 24)
69 #define OPT_BG_COLOUR (OPTBASE + 25)
70 #define OPT_BL_COLOUR (OPTBASE + 26)
71 #define OPT_FG_COLOUR (OPTBASE + 27)
72 #define OPT_FONT (OPTBASE + 28)
73 #define OPT_NO_SHADOW (OPTBASE + 29)
74 #define OPT_SHADOW (OPTBASE + 30)
75 #define OPT_TITLE (OPTBASE + 31)
76 #define OPT_NO_TITLE (OPTBASE + 32)
77 #define OPT_SUBTITLE (OPTBASE + 33)
78 #define OPT_NO_SUBTITLE (OPTBASE + 34)
79 #define OPT_TIMESTAMP (OPTBASE + 35)
80 #define OPT_NO_TIMESTAMP (OPTBASE + 36)
81 #define OPT_GMT (OPTBASE + 37)
82 #define OPT_INFO (OPTBASE + 38)
83 #define OPT_NO_INFO (OPTBASE + 39)
84 #define OPT_UNDERLAY (OPTBASE + 40)
85 #define OPT_NO_UNDERLAY (OPTBASE + 41)
86 #define OPT_OVERLAY (OPTBASE + 42)
87 #define OPT_NO_OVERLAY (OPTBASE + 43)
88 #define OPT_JPEG (OPTBASE + 44)
89 #define OPT_PNG (OPTBASE + 45)
90 #define OPT_SAVE (OPTBASE + 46)
91 #define OPT_EXEC (OPTBASE + 47)
92 #define OPT_DUMPFRAME (OPTBASE + 48)
93 #define OPT_FPS (OPTBASE + 49)
97 /* List of options. */
99 const struct option
*long_opts
;
101 /* When reading from the command line. */
104 /* When reading from a configuration file. */
118 /* General options. */
121 unsigned char background
;
126 /* Capture start time. */
129 /* Device options. */
133 unsigned long frequency
;
138 /* Image capture options. */
143 unsigned int skipframes
;
145 src_option_t
**option
;
150 fswebcam_job_t
**job
;
152 /* Banner options. */
165 /* Overlay options. */
169 /* Output options. */
176 #ifdef USE_32BIT_BUFFER
178 typedef uint32_t avgbmp_t
;
179 #define MAX_FRAMES (UINT32_MAX >> 8)
183 typedef uint16_t avgbmp_t
;
184 #define MAX_FRAMES (UINT16_MAX >> 8)
188 volatile char received_sigusr1
= 0;
189 volatile char received_sighup
= 0;
190 volatile char received_sigterm
= 0;
192 void fswc_signal_usr1_handler(int signum
)
194 /* Catches SIGUSR1 */
195 INFO("Caught signal SIGUSR1.");
196 received_sigusr1
= 1;
199 void fswc_signal_hup_handler(int signum
)
202 INFO("Caught signal SIGHUP.");
206 void fswc_signal_term_handler(int signum
)
210 /* Catches SIGTERM and SIGINT */
213 case SIGTERM
: signame
= "SIGTERM"; break;
214 case SIGINT
: signame
= "SIGINT"; break;
215 default: signame
= "Unknown"; break;
218 INFO("Caught signal %s", signame
);
219 received_sigterm
= 1;
222 int fswc_setup_signals()
224 signal(SIGUSR1
, fswc_signal_usr1_handler
);
225 signal(SIGHUP
, fswc_signal_hup_handler
);
226 signal(SIGTERM
, fswc_signal_term_handler
);
227 signal(SIGINT
, fswc_signal_term_handler
);
232 char *fswc_strftime(char *dst
, size_t max
, char *src
,
233 time_t timestamp
, int gmt
)
235 struct tm tm_timestamp
;
237 /* Set the time structure. */
238 if(gmt
) gmtime_r(×tamp
, &tm_timestamp
);
239 else localtime_r(×tamp
, &tm_timestamp
);
242 strftime(dst
, max
, src
, &tm_timestamp
);
247 char *fswc_strduptime(char *src
, time_t timestamp
, int gmt
)
249 struct tm tm_timestamp
;
253 if(!src
) return(NULL
);
255 /* Set the time structure. */
256 if(gmt
) gmtime_r(×tamp
, &tm_timestamp
);
257 else localtime_r(×tamp
, &tm_timestamp
);
265 char *t
= realloc(dst
, l
);
276 r
= strftime(dst
, l
, src
, &tm_timestamp
);
278 if(r
> 0 && r
< l
) return(dst
);
279 if(r
== 0 && *dst
== '\0') return(dst
);
287 void fswc_DrawText(gdImagePtr im
, char *font
, double size
,
288 int x
, int y
, char align
,
289 uint32_t colour
, char shadow
, char *text
)
298 uint32_t scolour
= colour
& 0xFF000000;
300 fswc_DrawText(im
, font
, size
, x
+ 1, y
+ 1,
301 align
, scolour
, 0, text
);
304 /* Correct alpha value for GD. */
305 colour
= (((colour
& 0xFF000000) / 2) & 0xFF000000) +
308 /* Pre-render the text. We use the results during alignment. */
309 err
= gdImageStringFT(NULL
, &brect
[0], colour
, font
, size
, 0.0, 0, 0, text
);
316 /* Adjust the coordinates according to the alignment. */
319 case ALIGN_CENTER
: x
-= brect
[4] / 2; break;
320 case ALIGN_RIGHT
: x
-= brect
[4]; break;
323 /* Render the text onto the image. */
324 gdImageStringFT(im
, NULL
, colour
, font
, size
, 0.0, x
, y
, text
);
327 int fswc_add_image_png(src_t
*src
, avgbmp_t
*abitmap
)
332 im
= gdImageCreateFromPngPtr(src
->length
, src
->img
);
335 for(y
= 0; y
< src
->height
; y
++)
336 for(x
= 0; x
< src
->width
; x
++)
338 int c
= gdImageGetPixel(im
, x
, y
);
340 *(abitmap
++) += (c
& 0xFF0000) >> 16;
341 *(abitmap
++) += (c
& 0x00FF00) >> 8;
342 *(abitmap
++) += (c
& 0x0000FF);
350 int fswc_verify_jpeg_dht(uint8_t *src
, uint32_t lsrc
,
351 uint8_t **dst
, uint32_t *ldst
)
353 /* This function is based on a patch provided by Scott J. Bertin. */
355 static unsigned char dht
[] =
357 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01,
358 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
359 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
360 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02,
361 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d,
362 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31,
363 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32,
364 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52,
365 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
366 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
367 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45,
368 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57,
369 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
370 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83,
371 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
372 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
373 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
374 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
375 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8,
376 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8,
377 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
378 0xf9, 0xfa, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01,
379 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
380 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
381 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04,
382 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01,
383 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41,
384 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14,
385 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
386 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25,
387 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a,
388 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46,
389 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
390 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
391 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83,
392 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
393 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
394 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
395 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
396 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8,
397 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
398 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa
400 uint8_t *p
, *i
= NULL
;
402 /* By default we simply return the source image. */
406 /* Scan for an existing DHT segment or the first SOS segment. */
407 for(p
= src
+ 2; p
- src
< lsrc
- 3 && i
== NULL
; )
409 if(*(p
++) != 0xFF) continue;
411 if(*p
== 0xD9) break; /* JPEG_EOI */
412 if(*p
== 0xC4) return(0); /* JPEG_DHT */
413 if(*p
== 0xDA && !i
) i
= p
- 1; /* JPEG_SOS */
415 /* Move to next segment. */
416 p
+= (p
[1] << 8) + p
[2];
419 /* If no SOS was found, insert the DHT directly after the SOI. */
420 if(i
== NULL
) i
= src
+ 2;
422 DEBUG("Inserting DHT segment into JPEG frame.");
424 *ldst
= lsrc
+ sizeof(dht
);
425 *dst
= malloc(*ldst
);
428 ERROR("Out of memory.");
432 /* Copy the JPEG data, inserting the DHT segment. */
433 memcpy((p
= *dst
), src
, i
- src
);
434 memcpy((p
+= i
- src
), dht
, sizeof(dht
));
435 memcpy((p
+= sizeof(dht
)), i
, lsrc
- (i
- src
));
440 int fswc_add_image_jpeg(src_t
*src
, avgbmp_t
*abitmap
)
442 uint32_t x
, y
, hlength
;
443 uint8_t *himg
= NULL
;
447 /* MJPEG data may lack the DHT segment required for decoding... */
448 i
= fswc_verify_jpeg_dht(src
->img
, src
->length
, &himg
, &hlength
);
450 im
= gdImageCreateFromJpegPtr(hlength
, himg
);
451 if(i
== 1) free(himg
);
455 for(y
= 0; y
< src
->height
; y
++)
456 for(x
= 0; x
< src
->width
; x
++)
458 int c
= gdImageGetPixel(im
, x
, y
);
460 *(abitmap
++) += (c
& 0xFF0000) >> 16;
461 *(abitmap
++) += (c
& 0x00FF00) >> 8;
462 *(abitmap
++) += (c
& 0x0000FF);
470 int fswc_add_image_rgb32(src_t
*src
, avgbmp_t
*abitmap
)
472 uint8_t *img
= (uint8_t *) src
->img
;
473 uint32_t i
= src
->width
* src
->height
;
475 if(src
->length
<< 2 < i
) return(-1);
479 *(abitmap
++) += *(img
++);
480 *(abitmap
++) += *(img
++);
481 *(abitmap
++) += *(img
++);
488 int fswc_add_image_bgr32(src_t
*src
, avgbmp_t
*abitmap
)
490 uint8_t *img
= (uint8_t *) src
->img
;
491 uint32_t p
, i
= src
->width
* src
->height
;
493 if(src
->length
<< 2 < i
) return(-1);
495 for(p
= 0; p
< i
; p
+= 4)
497 abitmap
[0] += img
[2];
498 abitmap
[1] += img
[1];
499 abitmap
[2] += img
[0];
507 int fswc_add_image_rgb24(src_t
*src
, avgbmp_t
*abitmap
)
509 uint8_t *img
= (uint8_t *) src
->img
;
510 uint32_t i
= src
->width
* src
->height
* 3;
512 if(src
->length
< i
) return(-1);
513 while(i
-- > 0) *(abitmap
++) += *(img
++);
518 int fswc_add_image_bgr24(src_t
*src
, avgbmp_t
*abitmap
)
520 uint8_t *img
= (uint8_t *) src
->img
;
521 uint32_t p
, i
= src
->width
* src
->height
* 3;
523 if(src
->length
< i
) return(-1);
525 for(p
= 0; p
< src
->length
; p
+= 3)
527 abitmap
[0] += img
[2];
528 abitmap
[1] += img
[1];
529 abitmap
[2] += img
[0];
537 int fswc_add_image_rgb565(src_t
*src
, avgbmp_t
*abitmap
)
539 uint16_t *img
= (uint16_t *) src
->img
;
540 uint32_t i
= src
->width
* src
->height
;
542 if(src
->length
>> 1 < i
) return(-1);
548 r
= (*img
& 0xF800) >> 8;
549 g
= (*img
& 0x7E0) >> 3;
550 b
= (*img
& 0x1F) << 3;
552 *(abitmap
++) += r
+ (r
>> 5);
553 *(abitmap
++) += g
+ (g
>> 6);
554 *(abitmap
++) += b
+ (b
>> 5);
562 int fswc_add_image_rgb555(src_t
*src
, avgbmp_t
*abitmap
)
564 uint16_t *img
= (uint16_t *) src
->img
;
565 uint32_t i
= src
->width
* src
->height
;
567 if(src
->length
>> 1 < i
) return(-1);
573 r
= (*img
& 0x7C00) >> 7;
574 g
= (*img
& 0x3E0) >> 2;
575 b
= (*img
& 0x1F) << 3;
577 *(abitmap
++) += r
+ (r
>> 5);
578 *(abitmap
++) += g
+ (g
>> 5);
579 *(abitmap
++) += b
+ (b
>> 5);
587 int fswc_add_image_bayer(src_t
*src
, avgbmp_t
*abitmap
)
589 uint8_t *img
= src
->img
;
590 uint32_t x
= 0, y
= 0;
591 uint32_t w
= src
->width
, h
= src
->height
;
594 if(src
->length
< i
) return(-1);
596 /* SBGGR8 bayer pattern:
603 * SGBRG8 bayer pattern:
618 /* Setup pointers to this pixel's neighbours. */
628 /* Juggle pointers if they are out of bounds. */
629 if(!y
) { p
[0]=p
[5]; p
[1]=p
[6]; p
[2]=p
[7]; }
630 else if(y
== h
- 1) { p
[5]=p
[0]; p
[6]=p
[1]; p
[7]=p
[2]; }
631 if(!x
) { p
[0]=p
[2]; p
[3]=p
[4]; p
[5]=p
[7]; }
632 else if(x
== w
- 1) { p
[2]=p
[0]; p
[4]=p
[3]; p
[7]=p
[5]; }
634 /* Average matching neighbours. */
635 hn
= (*p
[3] + *p
[4]) / 2;
636 vn
= (*p
[1] + *p
[6]) / 2;
637 di
= (*p
[0] + *p
[2] + *p
[5] + *p
[7]) / 4;
640 mode
= (x
+ y
) & 0x01;
641 if(src
->palette
== SRC_PAL_SGBRG8
)
647 if(y
& 0x01) { r
= hn
; b
= vn
; }
648 else { r
= vn
; b
= hn
; }
650 else if(y
& 0x01) { r
= *img
; g
= (vn
+ hn
) / 2; b
= di
; }
651 else { b
= *img
; g
= (vn
+ hn
) / 2; r
= di
; }
657 /* Move to the next pixel (or line) */
658 if(++x
== w
) { x
= 0; y
++; }
665 /* The following YUV functions are based on code by Vincent Hourdin.
666 * http://vinvin.dyndns.org/projects/
668 * Faster integer maths from camE by Tom Gilbert.
669 * http://linuxbrit.co.uk/camE/
672 int fswc_add_image_yuyv(src_t
*src
, avgbmp_t
*abitmap
)
677 if(src
->length
< (src
->width
* src
->height
* 2)) return(-1);
679 /* YUYV and UYVY are very similar and so *
680 * are both handled by this one function. */
682 ptr
= (uint8_t *) src
->img
;
685 for(y
= 0; y
< src
->height
; y
++)
687 for(x
= 0; x
< src
->width
; x
++)
692 if(src
->palette
== SRC_PAL_UYVY
)
694 if(!z
) y
= ptr
[1] << 8;
695 else y
= ptr
[3] << 8;
700 else /* SRC_PAL_YUYV */
702 if(!z
) y
= ptr
[0] << 8;
703 else y
= ptr
[2] << 8;
709 r
= (y
+ (359 * v
)) >> 8;
710 g
= (y
- (88 * u
) - (183 * v
)) >> 8;
711 b
= (y
+ (454 * u
)) >> 8;
713 *(abitmap
++) += CLIP(r
, 0x00, 0xFF);
714 *(abitmap
++) += CLIP(g
, 0x00, 0xFF);
715 *(abitmap
++) += CLIP(b
, 0x00, 0xFF);
728 int fswc_add_image_yuv420p(src_t
*src
, avgbmp_t
*abitmap
)
730 uint8_t *yptr
, *uptr
, *vptr
;
733 if(src
->length
< (src
->width
* src
->height
* 3) / 2) return(-1);
735 /* Setup pointers to Y, U and V buffers. */
736 yptr
= (uint8_t *) src
->img
;
737 uptr
= yptr
+ (src
->width
* src
->height
);
738 vptr
= uptr
+ (src
->width
* src
->height
/ 4);
742 for(y
= 0; y
< src
->height
; y
++)
744 for(x
= 0; x
< src
->width
; x
++)
753 r
= (y
+ (359 * v
)) >> 8;
754 g
= (y
- (88 * u
) - (183 * v
)) >> 8;
755 b
= (y
+ (454 * u
)) >> 8;
757 *(abitmap
++) += CLIP(r
, 0x00, 0xFF);
758 *(abitmap
++) += CLIP(g
, 0x00, 0xFF);
759 *(abitmap
++) += CLIP(b
, 0x00, 0xFF);
764 if(!(y
& 1)) p
-= src
->width
/ 2;
770 int fswc_add_image_nv12mb(src_t
*src
, avgbmp_t
*abitmap
)
775 if(src
->length
!= (src
->width
* src
->height
* 3) / 2) return(-1);
777 bw
= src
->width
>> 4;
779 for(y
= 0; y
< src
->height
; y
++)
781 for(x
= 0; x
< src
->width
; x
++)
792 py
+= ((by
* bw
) + bx
) * 0x100;
793 py
+= ((y
- (by
<< 4)) * 0x10) + (x
- (bx
<< 4));
797 puv
= src
->img
+ (src
->width
* src
->height
);
798 puv
+= ((by
* bw
) + bx
) * 0x100;
799 puv
+= (((y
/ 2) - (by
<< 4)) * 0x10) + ((x
- (bx
<< 4)) &~ 1);
805 cr
= (cy
+ (359 * cv
)) >> 8;
806 cg
= (cy
- (88 * cu
) - (183 * cv
)) >> 8;
807 cb
= (cy
+ (454 * cu
)) >> 8;
809 *(abitmap
++) += CLIP(cr
, 0x00, 0xFF);
810 *(abitmap
++) += CLIP(cg
, 0x00, 0xFF);
811 *(abitmap
++) += CLIP(cb
, 0x00, 0xFF);
818 int fswc_add_image_grey(src_t
*src
, avgbmp_t
*abitmap
)
820 uint8_t *bitmap
= (uint8_t *) src
->img
;
821 uint32_t i
= src
->width
* src
->height
;
823 if(src
->length
< i
) return(-1);
827 *(abitmap
++) += *bitmap
;
828 *(abitmap
++) += *bitmap
;
829 *(abitmap
++) += *(bitmap
++);
835 int fswc_draw_overlay(fswebcam_config_t
*config
, char *filename
, gdImage
*image
){
839 if(!filename
) return(-1);
841 f
= fopen(filename
, "rb");
844 ERROR("Unable to open '%s'", filename
);
845 ERROR("fopen: %s", strerror(errno
));
849 overlay
= gdImageCreateFromPng(f
);
854 ERROR("Unable to read '%s'. Not a PNG image?", filename
);
858 gdImageCopy(image
, overlay
, 0, 0, 0, 0, overlay
->sx
, overlay
->sy
);
859 gdImageDestroy(overlay
);
864 int fswc_draw_banner(fswebcam_config_t
*config
, gdImage
*image
)
873 w
= gdImageSX(image
);
874 h
= gdImageSY(image
);
876 /* Create the timestamp text. */
877 fswc_strftime(timestamp
, 200, config
->timestamp
,
878 config
->start
, config
->gmt
);
880 /* Calculate the position and height of the banner. */
882 height
= config
->fontsize
+ (spacing
* 2);
884 if(config
->subtitle
|| config
->info
)
885 height
+= config
->fontsize
* 0.8 + spacing
;
888 if(config
->banner
== BOTTOM_BANNER
) top
= h
- height
;
890 /* Draw the banner line. */
891 if(config
->banner
== TOP_BANNER
)
893 gdImageFilledRectangle(image
,
900 gdImageFilledRectangle(image
,
906 /* Draw the background box. */
907 gdImageFilledRectangle(image
,
912 y
= top
+ spacing
+ config
->fontsize
;
914 /* Draw the title. */
915 fswc_DrawText(image
, config
->font
, config
->fontsize
,
916 spacing
, y
, ALIGN_LEFT
,
917 config
->fg_colour
, config
->shadow
, config
->title
);
919 /* Draw the timestamp. */
920 fswc_DrawText(image
, config
->font
, config
->fontsize
* 0.8,
921 w
- spacing
, y
, ALIGN_RIGHT
,
922 config
->fg_colour
, config
->shadow
, timestamp
);
924 y
+= spacing
+ config
->fontsize
* 0.8;
926 /* Draw the sub-title. */
927 fswc_DrawText(image
, config
->font
, config
->fontsize
* 0.8,
928 spacing
, y
, ALIGN_LEFT
,
929 config
->fg_colour
, config
->shadow
, config
->subtitle
);
931 /* Draw the info text. */
932 fswc_DrawText(image
, config
->font
, config
->fontsize
* 0.7,
933 w
- spacing
, y
, ALIGN_RIGHT
,
934 config
->fg_colour
, config
->shadow
, config
->info
);
939 gdImage
* fswc_gdImageDuplicate(gdImage
* src
)
943 dst
= gdImageCreateTrueColor(gdImageSX(src
), gdImageSY(src
));
944 if(!dst
) return(NULL
);
946 gdImageCopy(dst
, src
, 0, 0, 0, 0, gdImageSX(src
), gdImageSY(src
));
951 int fswc_output(fswebcam_config_t
*config
, char *name
, gdImage
*image
)
953 char filename
[FILENAME_MAX
];
957 if(!name
) return(-1);
958 if(!strncmp(name
, "-", 2) && config
->background
)
960 ERROR("stdout is unavailable in background mode.");
964 fswc_strftime(filename
, FILENAME_MAX
, name
,
965 config
->start
, config
->gmt
);
967 /* Create a temporary image buffer. */
968 im
= fswc_gdImageDuplicate(image
);
971 ERROR("Out of memory.");
975 /* Draw the underlay. */
976 fswc_draw_overlay(config
, config
->underlay
, im
);
978 /* Draw the banner. */
979 if(config
->banner
!= NO_BANNER
)
983 /* Check if drawing text works */
984 err
= gdImageStringFT(NULL
, NULL
, 0, config
->font
, config
->fontsize
, 0.0, 0, 0, "");
986 if(!err
) fswc_draw_banner(config
, im
);
989 /* Can't load the font - display a warning */
990 WARN("Unable to load font '%s': %s", config
->font
, err
);
991 WARN("Disabling the the banner.");
995 /* Draw the overlay. */
996 fswc_draw_overlay(config
, config
->overlay
, im
);
998 /* Write to a file if a filename was given, otherwise stdout. */
999 if(strncmp(name
, "-", 2)) f
= fopen(filename
, "wb");
1004 ERROR("Error opening file for output: %s", filename
);
1005 ERROR("fopen: %s", strerror(errno
));
1010 /* Write the compressed image. */
1011 switch(config
->format
)
1014 MSG("Writing JPEG image to '%s'.", filename
);
1015 gdImageJpeg(im
, f
, config
->compression
);
1018 MSG("Writing PNG image to '%s'.", filename
);
1019 gdImagePngEx(im
, f
, config
->compression
);
1023 if(f
!= stdout
) fclose(f
);
1030 int fswc_exec(fswebcam_config_t
*config
, char *cmd
)
1035 cmdline
= fswc_strduptime(cmd
, config
->start
, config
->gmt
);
1036 if(!cmdline
) return(-1);
1038 MSG("Executing '%s'...", cmdline
);
1040 p
= popen(cmdline
, "r");
1050 if(!fgets(line
, 1024, p
)) break;
1052 while((n
= strchr(line
, '\n'))) *n
= '\0';
1060 ERROR("popen: %s", strerror(errno
));
1067 int fswc_grab(fswebcam_config_t
*config
)
1071 avgbmp_t
*abitmap
, *pbitmap
;
1072 gdImage
*image
, *original
;
1076 /* Record the start time. */
1077 config
->start
= time(NULL
);
1079 /* Set source options... */
1080 memset(&src
, 0, sizeof(src
));
1081 src
.input
= config
->input
;
1082 src
.tuner
= config
->tuner
;
1083 src
.frequency
= config
->frequency
;
1084 src
.delay
= config
->delay
;
1085 src
.timeout
= 10; /* seconds */
1086 src
.use_read
= config
->use_read
;
1087 src
.list
= config
->list
;
1088 src
.palette
= config
->palette
;
1089 src
.width
= config
->width
;
1090 src
.height
= config
->height
;
1091 src
.fps
= config
->fps
;
1092 src
.option
= config
->option
;
1094 HEAD("--- Opening %s...", config
->device
);
1096 if(src_open(&src
, config
->device
) == -1) return(-1);
1098 /* The source may have adjusted the width and height we passed
1099 * to it. Update the main config to match. */
1100 config
->width
= src
.width
;
1101 config
->height
= src
.height
;
1103 /* Allocate memory for the average bitmap buffer. */
1104 abitmap
= calloc(config
->width
* config
->height
* 3, sizeof(avgbmp_t
));
1107 ERROR("Out of memory.");
1111 if(config
->frames
== 1) HEAD("--- Capturing frame...");
1112 else HEAD("--- Capturing %i frames...", config
->frames
);
1114 if(config
->skipframes
== 1) MSG("Skipping frame...");
1115 else if(config
->skipframes
> 1) MSG("Skipping %i frames...", config
->skipframes
);
1117 /* Grab (and do nothing with) the skipped frames. */
1118 for(frame
= 0; frame
< config
->skipframes
; frame
++)
1119 if(src_grab(&src
) == -1) break;
1121 /* If frames where skipped, inform when normal capture begins. */
1122 if(config
->skipframes
) MSG("Capturing %i frames...", config
->frames
);
1124 /* Grab the requested number of frames. */
1125 for(frame
= 0; frame
< config
->frames
; frame
++)
1127 if(src_grab(&src
) == -1) break;
1129 if(!frame
&& config
->dumpframe
)
1131 /* Dump the raw data from the first frame to file. */
1134 MSG("Dumping raw frame to '%s'...", config
->dumpframe
);
1136 f
= fopen(config
->dumpframe
, "wb");
1137 if(!f
) ERROR("fopen: %s", strerror(errno
));
1141 l
= fwrite(src
.img
, 1, src
.length
, f
);
1146 /* Add frame to the average bitmap. */
1150 fswc_add_image_png(&src
, abitmap
);
1154 fswc_add_image_jpeg(&src
, abitmap
);
1157 fswc_add_image_rgb32(&src
, abitmap
);
1160 fswc_add_image_bgr32(&src
, abitmap
);
1163 fswc_add_image_rgb24(&src
, abitmap
);
1166 fswc_add_image_bgr24(&src
, abitmap
);
1169 case SRC_PAL_SGBRG8
:
1170 fswc_add_image_bayer(&src
, abitmap
);
1174 fswc_add_image_yuyv(&src
, abitmap
);
1176 case SRC_PAL_YUV420P
:
1177 fswc_add_image_yuv420p(&src
, abitmap
);
1179 case SRC_PAL_NV12MB
:
1180 fswc_add_image_nv12mb(&src
, abitmap
);
1182 case SRC_PAL_RGB565
:
1183 fswc_add_image_rgb565(&src
, abitmap
);
1185 case SRC_PAL_RGB555
:
1186 fswc_add_image_rgb555(&src
, abitmap
);
1189 fswc_add_image_grey(&src
, abitmap
);
1194 /* We are now finished with the capture card. */
1197 /* Fail if no frames where captured. */
1200 ERROR("No frames captured.");
1205 HEAD("--- Processing captured image...");
1207 /* Copy the average bitmap image to a gdImage. */
1208 original
= gdImageCreateTrueColor(config
->width
, config
->height
);
1211 ERROR("Out of memory.");
1217 for(y
= 0; y
< config
->height
; y
++)
1218 for(x
= 0; x
< config
->width
; x
++)
1224 colour
= (*(pbitmap
++) / config
->frames
) << 16;
1225 colour
+= (*(pbitmap
++) / config
->frames
) << 8;
1226 colour
+= (*(pbitmap
++) / config
->frames
);
1228 gdImageSetPixel(original
, px
, py
, colour
);
1233 /* Make a copy of the original image. */
1234 image
= fswc_gdImageDuplicate(original
);
1237 ERROR("Out of memory.");
1238 gdImageDestroy(image
);
1242 /* Set the default values for this run. */
1243 if(config
->font
) free(config
->font
);
1244 if(config
->title
) free(config
->title
);
1245 if(config
->subtitle
) free(config
->subtitle
);
1246 if(config
->timestamp
) free(config
->timestamp
);
1247 if(config
->info
) free(config
->info
);
1248 if(config
->underlay
) free(config
->underlay
);
1249 if(config
->overlay
) free(config
->overlay
);
1250 if(config
->filename
) free(config
->filename
);
1252 config
->banner
= BOTTOM_BANNER
;
1253 config
->bg_colour
= 0x40263A93;
1254 config
->bl_colour
= 0x00FF0000;
1255 config
->fg_colour
= 0x00FFFFFF;
1256 config
->font
= strdup("luxisr");
1257 config
->fontsize
= 10;
1259 config
->title
= NULL
;
1260 config
->subtitle
= NULL
;
1261 config
->timestamp
= strdup("%Y-%m-%d %H:%M (%Z)");
1262 config
->info
= NULL
;
1263 config
->underlay
= NULL
;
1264 config
->overlay
= NULL
;
1265 config
->filename
= NULL
;
1266 config
->format
= FORMAT_JPEG
;
1267 config
->compression
= -1;
1271 /* Run through the jobs list. */
1272 for(x
= 0; x
< config
->jobs
; x
++)
1274 uint16_t id
= config
->job
[x
]->id
;
1275 char *options
= config
->job
[x
]->options
;
1279 case 1: /* A non-option argument: a filename. */
1281 fswc_output(config
, options
, image
);
1285 fswc_exec(config
, options
);
1289 gdImageDestroy(image
);
1290 image
= fswc_gdImageDuplicate(original
);
1294 image
= fx_flip(image
, options
);
1298 image
= fx_crop(image
, options
);
1302 image
= fx_scale(image
, options
);
1306 image
= fx_rotate(image
, options
);
1308 case OPT_DEINTERLACE
:
1310 image
= fx_deinterlace(image
, options
);
1314 image
= fx_invert(image
, options
);
1318 image
= fx_greyscale(image
, options
);
1322 MSG("Disabling banner.");
1323 config
->banner
= NO_BANNER
;
1325 case OPT_TOP_BANNER
:
1327 MSG("Putting banner at the top.");
1328 config
->banner
= TOP_BANNER
;
1330 case OPT_BOTTOM_BANNER
:
1332 MSG("Putting banner at the bottom.");
1333 config
->banner
= BOTTOM_BANNER
;
1337 MSG("Setting banner background colour to %s.", options
);
1338 if(sscanf(options
, "#%X", &config
->bg_colour
) != 1)
1339 WARN("Bad background colour: %s", options
);
1343 MSG("Setting banner line colour to %s.", options
);
1344 if(sscanf(options
, "#%X", &config
->bl_colour
) != 1)
1345 WARN("Bad line colour: %s", options
);
1349 MSG("Setting banner text colour to %s.", options
);
1350 if(sscanf(options
, "#%X", &config
->fg_colour
) != 1)
1351 WARN("Bad text colour: %s", options
);
1355 MSG("Setting font to %s.", options
);
1356 if(parse_font(options
, &config
->font
, &config
->fontsize
))
1357 WARN("Bad font: %s", options
);
1361 MSG("Disabling text shadow.");
1366 MSG("Enabling text shadow.");
1371 MSG("Setting title \"%s\".", options
);
1372 if(config
->title
) free(config
->title
);
1373 config
->title
= strdup(options
);
1377 MSG("Clearing title.");
1378 if(config
->title
) free(config
->title
);
1379 config
->title
= NULL
;
1383 MSG("Setting subtitle \"%s\".", options
);
1384 if(config
->subtitle
) free(config
->subtitle
);
1385 config
->subtitle
= strdup(options
);
1387 case OPT_NO_SUBTITLE
:
1389 MSG("Clearing subtitle.");
1390 if(config
->subtitle
) free(config
->subtitle
);
1391 config
->subtitle
= NULL
;
1395 MSG("Setting timestamp \"%s\".", options
);
1396 if(config
->timestamp
) free(config
->timestamp
);
1397 config
->timestamp
= strdup(options
);
1399 case OPT_NO_TIMESTAMP
:
1401 MSG("Clearing timestamp.");
1402 if(config
->timestamp
) free(config
->timestamp
);
1403 config
->timestamp
= NULL
;
1407 MSG("Setting info text \"%s\".", options
);
1408 if(config
->info
) free(config
->info
);
1409 config
->info
= strdup(options
);
1413 MSG("Clearing info text.");
1414 if(config
->info
) free(config
->info
);
1415 config
->info
= NULL
;
1419 MSG("Setting underlay image: %s", options
);
1420 if(config
->underlay
) free(config
->underlay
);
1421 config
->underlay
= strdup(options
);
1423 case OPT_NO_UNDERLAY
:
1425 MSG("Clearing underlay.");
1426 if(config
->underlay
) free(config
->underlay
);
1427 config
->underlay
= NULL
;
1431 MSG("Setting overlay image: %s", options
);
1432 if(config
->overlay
) free(config
->overlay
);
1433 config
->overlay
= strdup(options
);
1435 case OPT_NO_OVERLAY
:
1437 MSG("Clearing overlay image.");
1438 if(config
->overlay
) free(config
->overlay
);
1439 config
->overlay
= NULL
;
1443 MSG("Setting output format to JPEG, quality %i", atoi(options
));
1444 config
->format
= FORMAT_JPEG
;
1445 config
->compression
= atoi(options
);
1449 MSG("Setting output format to PNG, quality %i", atoi(options
));
1450 config
->format
= FORMAT_PNG
;
1451 config
->compression
= atoi(options
);
1456 gdImageDestroy(image
);
1457 gdImageDestroy(original
);
1459 if(modified
) WARN("There are unsaved changes to the image.");
1464 int fswc_openlog(fswebcam_config_t
*config
)
1469 /* Get the first part of the filename. */
1470 s
= argdup(config
->logfile
, ":", 0, 0);
1473 ERROR("Out of memory.");
1477 if(!strcasecmp(s
, "file"))
1481 s
= argdup(config
->logfile
, ":", 1, 0);
1484 ERROR("No log file was specified.");
1488 else if(!strcasecmp(s
, "syslog"))
1501 int fswc_background(fswebcam_config_t
*config
)
1505 /* Silence the output if not logging to a file. */
1506 if(!config
->logfile
) log_set_fd(-1);
1508 /* Fork into the background. */
1513 ERROR("Error going into the background.");
1514 ERROR("fork: %s", strerror(errno
));
1518 /* Is this the parent process? If so, end it. */
1519 if(pid
> 0) exit(0);
1523 /* Create a new SID for the child process. */
1527 ERROR("Error going into the background.");
1528 ERROR("setsid: %s", strerror(errno
));
1532 close(STDIN_FILENO
);
1533 close(STDOUT_FILENO
);
1534 close(STDERR_FILENO
);
1539 int fswc_savepid(fswebcam_config_t
*config
)
1541 FILE *fpid
= fopen(config
->pidfile
, "wt");
1545 ERROR("Error saving PID to file '%s'",config
->pidfile
);
1546 ERROR("fopen: %s", strerror(errno
));
1551 fprintf(fpid
, "%i\n", getpid());
1557 int fswc_find_palette(char *name
)
1561 /* Scan through the palette table until a match is found. */
1562 for(i
= 0; src_palette
[i
].name
!= NULL
; i
++)
1564 /* If a match was found, return the index. */
1565 if(!strcasecmp(src_palette
[i
].name
, name
)) return(i
);
1568 /* No match was found. */
1569 ERROR("Unrecognised palette format \"%s\". Supported formats:", name
);
1571 for(i
= 0; src_palette
[i
].name
!= NULL
; i
++)
1572 ERROR("%s", src_palette
[i
].name
);
1579 printf("Usage: fswebcam [<options>] <filename> [[<options>] <filename> ... ]\n"
1583 " -?, --help Display this help page and exit.\n"
1584 " -c, --config <filename> Load configuration from file.\n"
1585 " -q, --quiet Hides all messages except for errors.\n"
1586 " -v, --verbose Displays extra messages while capturing\n"
1587 " --version Displays the version and exits.\n"
1588 " -l, --loop <seconds> Run in loop mode.\n"
1589 " -b, --background Run in the background.\n"
1590 " -o, --output <filename> Output the log to a file.\n"
1591 " -d, --device <name> Sets the source to use.\n"
1592 " -i, --input <number/name> Selects the input to use.\n"
1593 " -t, --tuner <number> Selects the tuner to use.\n"
1594 " -f, --frequency <number> Selects the frequency use.\n"
1595 " -p, --palette <name> Selects the palette format to use.\n"
1596 " -D, --delay <number> Sets the pre-capture delay time. (seconds)\n"
1597 " -r, --resolution <size> Sets the capture resolution.\n"
1598 " --fps <framerate> Sets the capture frame rate.\n"
1599 " -F, --frames <number> Sets the number of frames to capture.\n"
1600 " -S, --skip <number> Sets the number of frames to skip.\n"
1601 " --dumpframe <filename> Dump a raw frame frame to file.\n"
1602 " -s, --set <name>=<value> Sets a control value.\n"
1603 " --revert Restores original captured image.\n"
1604 " --flip <direction> Flips the image. (h, v)\n"
1605 " --crop <size>[,<offset>] Crop a part of the image.\n"
1606 " --scale <size> Scales the image.\n"
1607 " --rotate <angle> Rotates the image in right angles.\n"
1608 " --deinterlace Reduces interlace artifacts.\n"
1609 " --invert Inverts the images colours.\n"
1610 " --greyscale Removes colour from the image.\n"
1611 " --no-banner Hides the banner.\n"
1612 " --top-banner Puts the banner at the top.\n"
1613 " --bottom-banner Puts the banner at the bottom. (Default)\n"
1614 " --banner-colour <colour> Sets the banner colour. (#AARRGGBB)\n"
1615 " --line-colour <colour> Sets the banner line colour.\n"
1616 " --text-colour <colour> Sets the text colour.\n"
1617 " --font <[name][:size]> Sets the font and/or size.\n"
1618 " --no-shadow Disables the text shadow.\n"
1619 " --shadow Enables the text shadow.\n"
1620 " --title <text> Sets the main title. (top left)\n"
1621 " --no-title Clears the main title.\n"
1622 " --subtitle <text> Sets the sub-title. (bottom left)\n"
1623 " --no-subtitle Clears the sub-title.\n"
1624 " --timestamp <format> Sets the timestamp format. (top right)\n"
1625 " --no-timestamp Clears the timestamp.\n"
1626 " --gmt Use GMT instead of local timezone.\n"
1627 " --info <text> Sets the info text. (bottom right)\n"
1628 " --no-info Clears the info text.\n"
1629 " --underlay <PNG image> Sets the underlay image.\n"
1630 " --no-underlay Clears the underlay.\n"
1631 " --overlay <PNG image> Sets the overlay image.\n"
1632 " --no-overlay Clears the overlay.\n"
1633 " --jpeg <factor> Outputs a JPEG image. (-1, 0 - 95)\n"
1634 " --png <factor> Outputs a PNG image. (-1, 0 - 10)\n"
1635 " --save <filename> Save image to file.\n"
1636 " --exec <command> Execute a command and wait for it to complete.\n"
1642 int fswc_add_job(fswebcam_config_t
*config
, uint16_t id
, char *options
)
1644 fswebcam_job_t
*job
;
1647 job
= malloc(sizeof(fswebcam_job_t
));
1650 ERROR("Out of memory.");
1655 if(options
) job
->options
= strdup(options
);
1656 else job
->options
= NULL
;
1658 /* Increase the size of the job queue. */
1659 n
= realloc(config
->job
, sizeof(fswebcam_job_t
*) * (config
->jobs
+ 1));
1662 ERROR("Out of memory.");
1664 if(job
->options
) free(job
->options
);
1672 /* Add the new job to the queue. */
1673 config
->job
[config
->jobs
++] = job
;
1678 int fswc_free_jobs(fswebcam_config_t
*config
)
1682 /* Scan through all the jobs and free the memory. */
1683 for(i
= 0; i
< config
->jobs
; i
++)
1685 if(config
->job
[i
]->options
) free(config
->job
[i
]->options
);
1686 free(config
->job
[i
]);
1689 /* Free the job queue, set settings to zero. */
1697 int fswc_set_option(fswebcam_config_t
*config
, char *option
)
1701 if(!option
) return(-1);
1703 name
= strdup(option
);
1706 ERROR("Out of memory.");
1710 value
= strchr(name
, '=');
1714 if(*value
== '\0') value
= NULL
;
1717 src_set_option(&config
->option
, name
, value
);
1724 int fswc_getopt_file(fswc_getopt_t
*s
)
1730 while(fgets(line
, 1024, s
->f
))
1733 strtrim(line
, WHITESPACE
);
1734 arg
= argdup(line
, WHITESPACE
, 0, 0);
1737 if(*arg
== '#') continue;
1739 /* Find argument in the list. */
1740 opt
= (struct option
*) s
->long_opts
;
1743 if(!strcasecmp(opt
->name
, arg
)) break;
1749 ERROR("Unknown argument: %s", arg
);
1750 WARN("%s,%i: %s", s
->filename
, s
->line
, line
);
1757 ERROR("You can't use config from a configuration file.");
1758 WARN("%s,%i: %s", s
->filename
, s
->line
, line
);
1765 val
= argdup(line
, WHITESPACE
, 1, 0);
1774 /* Have we reached the end of the file? */
1775 if(feof(s
->f
)) return(-1);
1777 /* No.. there has been an error. */
1778 ERROR("fread: %s", strerror(errno
));
1783 int fswc_getopt(fswc_getopt_t
*s
, int argc
, char *argv
[])
1787 /* If a conf file is opened, read from that. */
1790 /* Read until we find an argument, error or EOF. */
1791 c
= fswc_getopt_file(s
);
1799 if(c
== -2) return('!');
1804 /* Read from the command line. */
1805 c
= getopt_long(argc
, argv
, s
->opts
, s
->long_opts
, &s
->opt_index
);
1810 int fswc_getopts(fswebcam_config_t
*config
, int argc
, char *argv
[])
1814 static struct option long_opts
[] =
1816 {"help", no_argument
, 0, '?'},
1817 {"config", required_argument
, 0, 'c'},
1818 {"quiet", no_argument
, 0, 'q'},
1819 {"verbose", no_argument
, 0, 'v'},
1820 {"version", no_argument
, 0, OPT_VERSION
},
1821 {"loop", required_argument
, 0, 'l'},
1822 {"offset", required_argument
, 0, OPT_OFFSET
},
1823 {"background", no_argument
, 0, 'b'},
1824 {"pid", required_argument
, 0, OPT_PID
},
1825 {"log", required_argument
, 0, 'L'},
1826 {"device", required_argument
, 0, 'd'},
1827 {"input", required_argument
, 0, 'i'},
1828 {"list-inputs", no_argument
, 0, OPT_LIST_INPUTS
},
1829 {"tuner", required_argument
, 0, 't'},
1830 {"list-tuners", no_argument
, 0, OPT_LIST_TUNERS
},
1831 {"frequency", required_argument
, 0, 'f'},
1832 {"delay", required_argument
, 0, 'D'},
1833 {"resolution", required_argument
, 0, 'r'},
1834 {"list-framesizes", no_argument
, 0, OPT_LIST_FRAMESIZES
},
1835 {"list-framerates", no_argument
, 0, OPT_LIST_FRAMERATES
},
1836 {"frames", required_argument
, 0, 'F'},
1837 {"skip", required_argument
, 0, 'S'},
1838 {"palette", required_argument
, 0, 'p'},
1839 {"dumpframe", required_argument
, 0, OPT_DUMPFRAME
},
1840 {"read", no_argument
, 0, 'R'},
1841 {"list-formats", no_argument
, 0, OPT_LIST_FORMATS
},
1842 {"set", required_argument
, 0, 's'},
1843 {"list-controls", no_argument
, 0, OPT_LIST_CONTROLS
},
1844 {"revert", no_argument
, 0, OPT_REVERT
},
1845 {"flip", required_argument
, 0, OPT_FLIP
},
1846 {"crop", required_argument
, 0, OPT_CROP
},
1847 {"scale", required_argument
, 0, OPT_SCALE
},
1848 {"rotate", required_argument
, 0, OPT_ROTATE
},
1849 {"deinterlace", no_argument
, 0, OPT_DEINTERLACE
},
1850 {"invert", no_argument
, 0, OPT_INVERT
},
1851 {"greyscale", no_argument
, 0, OPT_GREYSCALE
},
1852 {"no-banner", no_argument
, 0, OPT_NO_BANNER
},
1853 {"top-banner", no_argument
, 0, OPT_TOP_BANNER
},
1854 {"bottom-banner", no_argument
, 0, OPT_BOTTOM_BANNER
},
1855 {"banner-colour", required_argument
, 0, OPT_BG_COLOUR
},
1856 {"line-colour", required_argument
, 0, OPT_BL_COLOUR
},
1857 {"text-colour", required_argument
, 0, OPT_FG_COLOUR
},
1858 {"font", required_argument
, 0, OPT_FONT
},
1859 {"no-shadow", no_argument
, 0, OPT_NO_SHADOW
},
1860 {"shadow", no_argument
, 0, OPT_SHADOW
},
1861 {"title", required_argument
, 0, OPT_TITLE
},
1862 {"no-title", no_argument
, 0, OPT_NO_TITLE
},
1863 {"subtitle", required_argument
, 0, OPT_SUBTITLE
},
1864 {"no-subtitle", no_argument
, 0, OPT_NO_SUBTITLE
},
1865 {"timestamp", required_argument
, 0, OPT_TIMESTAMP
},
1866 {"no-timestamp", no_argument
, 0, OPT_NO_TIMESTAMP
},
1867 {"gmt", no_argument
, 0, OPT_GMT
},
1868 {"info", required_argument
, 0, OPT_INFO
},
1869 {"no-info", no_argument
, 0, OPT_NO_INFO
},
1870 {"underlay", required_argument
, 0, OPT_UNDERLAY
},
1871 {"no-underlay", no_argument
, 0, OPT_NO_UNDERLAY
},
1872 {"overlay", required_argument
, 0, OPT_OVERLAY
},
1873 {"no-overlay", no_argument
, 0, OPT_NO_OVERLAY
},
1874 {"jpeg", required_argument
, 0, OPT_JPEG
},
1875 {"png", required_argument
, 0, OPT_PNG
},
1876 {"save", required_argument
, 0, OPT_SAVE
},
1877 {"exec", required_argument
, 0, OPT_EXEC
},
1880 char *opts
= "-qc:vl:bL:d:i:t:f:D:r:F:s:S:p:R";
1883 s
.long_opts
= long_opts
;
1889 /* Set the defaults. */
1892 config
->background
= 0;
1893 config
->pidfile
= NULL
;
1894 config
->logfile
= NULL
;
1897 config
->device
= strdup("/dev/video0");
1898 config
->input
= NULL
;
1900 config
->frequency
= 0;
1902 config
->use_read
= 0;
1904 config
->width
= 384;
1905 config
->height
= 288;
1908 config
->skipframes
= 0;
1909 config
->palette
= SRC_PAL_ANY
;
1910 config
->option
= NULL
;
1911 config
->dumpframe
= NULL
;
1915 /* Don't report errors. */
1918 /* Reset getopt to ensure parsing begins at the first argument. */
1921 /* Parse the command line and any config files. */
1922 while((c
= fswc_getopt(&s
, argc
, argv
)) != -1)
1926 case '?': fswc_usage(); /* Command line error. */
1927 case '!': return(-1); /* Conf file error. */
1929 INFO("Reading configuration from '%s'...", optarg
);
1930 s
.f
= fopen(optarg
, "rt");
1933 ERROR("fopen: %s", strerror(errno
));
1938 s
.filename
= strdup(optarg
);
1941 fprintf(stderr
, "fswebcam %s\n", PACKAGE_VERSION
);
1952 config
->loop
= atol(optarg
);
1955 config
->offset
= atol(optarg
);
1958 config
->background
= -1;
1961 if(config
->pidfile
) free(config
->pidfile
);
1962 config
->pidfile
= strdup(optarg
);
1965 if(config
->logfile
) free(config
->logfile
);
1966 config
->logfile
= strdup(optarg
);
1969 if(config
->device
) free(config
->device
);
1970 config
->device
= strdup(optarg
);
1973 if(config
->input
) free(config
->input
);
1974 config
->input
= strdup(optarg
);
1976 case OPT_LIST_INPUTS
:
1977 config
->list
|= SRC_LIST_INPUTS
;
1980 config
->tuner
= atoi(optarg
);
1982 case OPT_LIST_TUNERS
:
1983 config
->list
|= SRC_LIST_TUNERS
;
1986 config
->frequency
= atof(optarg
) * 1000;
1989 config
->delay
= atoi(optarg
);
1992 config
->width
= argtol(optarg
, "x ", 0, 0, 10);
1993 config
->height
= argtol(optarg
, "x ", 1, 0, 10);
1996 config
->fps
= atoi(optarg
);
1999 config
->frames
= atoi(optarg
);
2002 config
->skipframes
= atoi(optarg
);
2005 fswc_set_option(config
, optarg
);
2007 case OPT_LIST_CONTROLS
:
2008 config
->list
|= SRC_LIST_CONTROLS
;
2011 config
->palette
= fswc_find_palette(optarg
);
2012 if(config
->palette
== -1) return(-1);
2015 config
->use_read
= -1;
2017 case OPT_LIST_FORMATS
:
2018 config
->list
|= SRC_LIST_FORMATS
;
2024 free(config
->dumpframe
);
2025 config
->dumpframe
= strdup(optarg
);
2028 /* All other options are added to the job queue. */
2029 fswc_add_job(config
, c
, optarg
);
2034 /* Do a sanity check on the options. */
2035 if(config
->frequency
< 0) config
->frequency
= 0;
2036 if(config
->width
< 1) config
->width
= 1;
2037 if(config
->height
< 1) config
->height
= 1;
2038 if(config
->frames
< 1) config
->frames
= 1;
2039 if(config
->frames
> MAX_FRAMES
)
2041 WARN("Requested %u frames, maximum is %u. Using that.",
2042 config
->frames
, MAX_FRAMES
);
2044 config
->frames
= MAX_FRAMES
;
2047 /* Correct offset if negative or out of range. */
2048 if(config
->offset
&& (config
->offset
%= (signed long) config
->loop
) < 0)
2049 config
->offset
+= config
->loop
;
2051 /* Free the config filename if set. */
2057 int fswc_free_config(fswebcam_config_t
*config
)
2059 free(config
->pidfile
);
2060 free(config
->logfile
);
2061 free(config
->device
);
2062 free(config
->input
);
2064 free(config
->dumpframe
);
2065 free(config
->title
);
2066 free(config
->subtitle
);
2067 free(config
->timestamp
);
2070 free(config
->underlay
);
2071 free(config
->overlay
);
2072 free(config
->filename
);
2074 src_free_options(&config
->option
);
2075 fswc_free_jobs(config
);
2077 memset(config
, 0, sizeof(fswebcam_config_t
));
2082 int main(int argc
, char *argv
[])
2084 fswebcam_config_t
*config
;
2086 /* Prepare the configuration structure. */
2087 config
= calloc(sizeof(fswebcam_config_t
), 1);
2090 WARN("Out of memory.");
2094 /* Set defaults and parse the command line. */
2095 if(fswc_getopts(config
, argc
, argv
)) return(-1);
2097 /* Open the log file if one was specified. */
2098 if(config
->logfile
&& fswc_openlog(config
)) return(-1);
2100 /* Go into the background if requested. */
2101 if(config
->background
&& fswc_background(config
)) return(-1);
2103 /* Save PID of requested. */
2104 if(config
->pidfile
&& fswc_savepid(config
)) return(-1);
2106 /* Setup signal handlers. */
2107 if(fswc_setup_signals()) return(-1);
2109 /* Capture the image(s). */
2110 if(!config
->loop
) fswc_grab(config
);
2113 /* Loop mode ... keep capturing images until terminated. */
2116 time_t capturetime
= time(NULL
);
2119 /* Calculate when the next image is due. */
2120 capturetime
-= (capturetime
% config
->loop
);
2121 capturetime
+= config
->loop
+ config
->offset
;
2123 /* Correct the capturetime if the offset pushes
2124 * it to far into the future. */
2125 if(capturetime
- time(NULL
) > config
->loop
)
2126 capturetime
-= config
->loop
;
2128 fswc_strftime(timestamp
, 32, "%Y-%m-%d %H:%M:%S (%Z)",
2129 capturetime
, config
->gmt
);
2131 MSG(">>> Next image due: %s", timestamp
);
2133 /* Wait until that time comes. */
2134 while(time(NULL
) < capturetime
)
2139 /* Reload configuration. */
2140 MSG("Received HUP signal... reloading configuration.");
2141 fswc_free_config(config
);
2142 fswc_getopts(config
, argc
, argv
);
2144 /* Clear hup signal. */
2145 received_sighup
= 0;
2148 if(received_sigusr1
)
2150 MSG("Received USR1 signal... Capturing image.");
2154 if(received_sigterm
)
2156 MSG("Received TERM signal... exiting.");
2161 if(received_sigterm
) break;
2163 /* Clear usr1 signal. */
2164 received_sigusr1
= 0;
2166 /* Capture the image. */
2171 /* Close the log file. */
2172 if(config
->logfile
) log_close();
2174 /* Free all used memory. */
2175 fswc_free_config(config
);