FPS option part 2
[fswebcam.git] / fswebcam.c
blob068cb2181714141d202d6ff2bb0da742e4ccf407
1 /* fswebcam - Small and simple webcam for *nix */
2 /*===========================================================*/
3 /* Copyright (C)2005-2009 Philip Heron <phil@firestorm.cx> */
4 /* */
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. */
10 #ifdef HAVE_CONFIG_H
11 #include "config.h"
12 #endif
14 #include <stdio.h>
15 #include <getopt.h>
16 #include <string.h>
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <time.h>
20 #include <gd.h>
21 #include <errno.h>
22 #include <signal.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include "log.h"
26 #include "src.h"
27 #include "effects.h"
28 #include "parse.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)
36 #define NO_BANNER (0)
37 #define TOP_BANNER (1)
38 #define BOTTOM_BANNER (2)
40 #define FORMAT_JPEG (0)
41 #define FORMAT_PNG (1)
43 #define OPTBASE (128)
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)
95 typedef struct {
97 /* List of options. */
98 char *opts;
99 const struct option *long_opts;
101 /* When reading from the command line. */
102 int opt_index;
104 /* When reading from a configuration file. */
105 char *filename;
106 FILE *f;
107 size_t line;
109 } fswc_getopt_t;
111 typedef struct {
112 uint16_t id;
113 char *options;
114 } fswebcam_job_t;
116 typedef struct {
118 /* General options. */
119 unsigned long loop;
120 signed long offset;
121 unsigned char background;
122 char *pidfile;
123 char *logfile;
124 char gmt;
126 /* Capture start time. */
127 time_t start;
129 /* Device options. */
130 char *device;
131 char *input;
132 unsigned char tuner;
133 unsigned long frequency;
134 unsigned long delay;
135 char use_read;
136 uint8_t list;
138 /* Image capture options. */
139 unsigned int width;
140 unsigned int height;
141 unsigned int frames;
142 unsigned int fps;
143 unsigned int skipframes;
144 int palette;
145 src_option_t **option;
146 char *dumpframe;
148 /* Job queue. */
149 uint8_t jobs;
150 fswebcam_job_t **job;
152 /* Banner options. */
153 char banner;
154 uint32_t bg_colour;
155 uint32_t bl_colour;
156 uint32_t fg_colour;
157 char *title;
158 char *subtitle;
159 char *timestamp;
160 char *info;
161 char *font;
162 int fontsize;
163 char shadow;
165 /* Overlay options. */
166 char *underlay;
167 char *overlay;
169 /* Output options. */
170 char *filename;
171 char format;
172 char compression;
174 } fswebcam_config_t;
176 #ifdef USE_32BIT_BUFFER
178 typedef uint32_t avgbmp_t;
179 #define MAX_FRAMES (UINT32_MAX >> 8)
181 #else
183 typedef uint16_t avgbmp_t;
184 #define MAX_FRAMES (UINT16_MAX >> 8)
186 #endif
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)
201 /* Catches SIGHUP */
202 INFO("Caught signal SIGHUP.");
203 received_sighup = 1;
206 void fswc_signal_term_handler(int signum)
208 char *signame;
210 /* Catches SIGTERM and SIGINT */
211 switch(signum)
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);
229 return(0);
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(&timestamp, &tm_timestamp);
239 else localtime_r(&timestamp, &tm_timestamp);
241 *dst = '\0';
242 strftime(dst, max, src, &tm_timestamp);
244 return(dst);
247 char *fswc_strduptime(char *src, time_t timestamp, int gmt)
249 struct tm tm_timestamp;
250 char *dst;
251 size_t l;
253 if(!src) return(NULL);
255 /* Set the time structure. */
256 if(gmt) gmtime_r(&timestamp, &tm_timestamp);
257 else localtime_r(&timestamp, &tm_timestamp);
259 dst = NULL;
260 l = strlen(src) * 2;
262 while(1)
264 size_t r;
265 char *t = realloc(dst, l);
267 if(!t)
269 free(dst);
270 return(NULL);
273 dst = t;
275 *dst = 1;
276 r = strftime(dst, l, src, &tm_timestamp);
278 if(r > 0 && r < l) return(dst);
279 if(r == 0 && *dst == '\0') return(dst);
281 l *= 2;
284 return(NULL);
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)
291 char *err;
292 int brect[8];
294 if(!text) return;
296 if(shadow)
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) +
306 (colour & 0xFFFFFF);
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);
310 if(err)
312 WARN("%s", err);
313 return;
316 /* Adjust the coordinates according to the alignment. */
317 switch(align)
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)
329 uint32_t x, y;
330 gdImage *im;
332 im = gdImageCreateFromPngPtr(src->length, src->img);
333 if(!im) return(-1);
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);
345 gdImageDestroy(im);
347 return(0);
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. */
403 *dst = src;
404 *ldst = lsrc;
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);
426 if(!*dst)
428 ERROR("Out of memory.");
429 return(-1);
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));
437 return(1);
440 int fswc_add_image_jpeg(src_t *src, avgbmp_t *abitmap)
442 uint32_t x, y, hlength;
443 uint8_t *himg = NULL;
444 gdImage *im;
445 int i;
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);
453 if(!im) return(-1);
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);
465 gdImageDestroy(im);
467 return(0);
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);
477 while(i-- > 0)
479 *(abitmap++) += *(img++);
480 *(abitmap++) += *(img++);
481 *(abitmap++) += *(img++);
482 img++;
485 return(0);
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];
500 abitmap += 3;
501 img += 4;
504 return(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++);
515 return(0);
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];
530 abitmap += 3;
531 img += 3;
534 return(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);
544 while(i-- > 0)
546 uint8_t r, g, b;
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);
556 img++;
559 return(0);
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);
569 while(i-- > 0)
571 uint8_t r, g, b;
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);
581 img++;
584 return(0);
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;
592 uint32_t i = w * h;
594 if(src->length < i) return(-1);
596 /* SBGGR8 bayer pattern:
598 * BGBGBGBGBG
599 * GRGRGRGRGR
600 * BGBGBGBGBG
601 * GRGRGRGRGR
603 * SGBRG8 bayer pattern:
605 * GBGBGBGBGB
606 * RGRGRGRGRG
607 * GBGBGBGBGB
608 * RGRGRGRGRG
611 while(i-- > 0)
613 uint8_t *p[8];
614 uint8_t hn, vn, di;
615 uint8_t r, g, b;
616 int mode;
618 /* Setup pointers to this pixel's neighbours. */
619 p[0] = img - w - 1;
620 p[1] = img - w;
621 p[2] = img - w + 1;
622 p[3] = img - 1;
623 p[4] = img + 1;
624 p[5] = img + w - 1;
625 p[6] = img + w;
626 p[7] = img + w + 1;
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;
639 /* Calculate RGB */
640 mode = (x + y) & 0x01;
641 if(src->palette == SRC_PAL_SGBRG8)
642 mode = ~mode;
644 if(mode)
646 g = *img;
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; }
653 *(abitmap++) += r;
654 *(abitmap++) += g;
655 *(abitmap++) += b;
657 /* Move to the next pixel (or line) */
658 if(++x == w) { x = 0; y++; }
659 img++;
662 return(0);
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)
674 uint8_t *ptr;
675 uint32_t x, y, z;
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;
683 z = 0;
685 for(y = 0; y < src->height; y++)
687 for(x = 0; x < src->width; x++)
689 int r, g, b;
690 int y, u, v;
692 if(src->palette == SRC_PAL_UYVY)
694 if(!z) y = ptr[1] << 8;
695 else y = ptr[3] << 8;
697 u = ptr[0] - 128;
698 v = ptr[2] - 128;
700 else /* SRC_PAL_YUYV */
702 if(!z) y = ptr[0] << 8;
703 else y = ptr[2] << 8;
705 u = ptr[1] - 128;
706 v = ptr[3] - 128;
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);
717 if(z++)
719 z = 0;
720 ptr += 4;
725 return(0);
728 int fswc_add_image_yuv420p(src_t *src, avgbmp_t *abitmap)
730 uint8_t *yptr, *uptr, *vptr;
731 uint32_t x, y, p, o;
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);
739 o = 0;
740 p = 0;
742 for(y = 0; y < src->height; y++)
744 for(x = 0; x < src->width; x++)
746 int r, g, b;
747 int y, u, v;
749 y = *(yptr++) << 8;
750 u = uptr[p] - 128;
751 v = vptr[p] - 128;
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);
761 if(x & 1) p++;
764 if(!(y & 1)) p -= src->width / 2;
767 return(0);
770 int fswc_add_image_nv12mb(src_t *src, avgbmp_t *abitmap)
772 uint32_t x, y;
773 uint32_t bw;
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++)
783 uint32_t bx, by;
784 int cy, cu, cv;
785 int cr, cg, cb;
786 uint8_t *py, *puv;
788 bx = x >> 4;
789 by = y >> 4;
791 py = src->img;
792 py += ((by * bw) + bx) * 0x100;
793 py += ((y - (by << 4)) * 0x10) + (x - (bx << 4));
795 by /= 2;
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);
801 cy = *py << 8;
802 cu = puv[0] - 128;
803 cv = puv[1] - 128;
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);
815 return(0);
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);
825 while(i-- > 0)
827 *(abitmap++) += *bitmap;
828 *(abitmap++) += *bitmap;
829 *(abitmap++) += *(bitmap++);
832 return(0);
835 int fswc_draw_overlay(fswebcam_config_t *config, char *filename, gdImage *image){
836 FILE *f;
837 gdImage *overlay;
839 if(!filename) return(-1);
841 f = fopen(filename, "rb");
842 if(!f)
844 ERROR("Unable to open '%s'", filename);
845 ERROR("fopen: %s", strerror(errno));
846 return(-1);
849 overlay = gdImageCreateFromPng(f);
850 fclose(f);
852 if(!overlay)
854 ERROR("Unable to read '%s'. Not a PNG image?", filename);
855 return(-1);
858 gdImageCopy(image, overlay, 0, 0, 0, 0, overlay->sx, overlay->sy);
859 gdImageDestroy(overlay);
861 return(0);
864 int fswc_draw_banner(fswebcam_config_t *config, gdImage *image)
866 char timestamp[200];
867 int w, h;
868 int height;
869 int spacing;
870 int top;
871 int y;
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. */
881 spacing = 4;
882 height = config->fontsize + (spacing * 2);
884 if(config->subtitle || config->info)
885 height += config->fontsize * 0.8 + spacing;
887 top = 0;
888 if(config->banner == BOTTOM_BANNER) top = h - height;
890 /* Draw the banner line. */
891 if(config->banner == TOP_BANNER)
893 gdImageFilledRectangle(image,
894 0, height + 1,
895 w, height + 2,
896 config->bl_colour);
898 else
900 gdImageFilledRectangle(image,
901 0, top - 2,
902 w, top - 1,
903 config->bl_colour);
906 /* Draw the background box. */
907 gdImageFilledRectangle(image,
908 0, top,
909 w, top + height,
910 config->bg_colour);
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);
936 return(0);
939 gdImage* fswc_gdImageDuplicate(gdImage* src)
941 gdImage *dst;
943 dst = gdImageCreateTrueColor(gdImageSX(src), gdImageSY(src));
944 if(!dst) return(NULL);
946 gdImageCopy(dst, src, 0, 0, 0, 0, gdImageSX(src), gdImageSY(src));
948 return(dst);
951 int fswc_output(fswebcam_config_t *config, char *name, gdImage *image)
953 char filename[FILENAME_MAX];
954 gdImage *im;
955 FILE *f;
957 if(!name) return(-1);
958 if(!strncmp(name, "-", 2) && config->background)
960 ERROR("stdout is unavailable in background mode.");
961 return(-1);
964 fswc_strftime(filename, FILENAME_MAX, name,
965 config->start, config->gmt);
967 /* Create a temporary image buffer. */
968 im = fswc_gdImageDuplicate(image);
969 if(!im)
971 ERROR("Out of memory.");
972 return(-1);
975 /* Draw the underlay. */
976 fswc_draw_overlay(config, config->underlay, im);
978 /* Draw the banner. */
979 if(config->banner != NO_BANNER)
981 char *err;
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);
987 else
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");
1000 else f = stdout;
1002 if(!f)
1004 ERROR("Error opening file for output: %s", filename);
1005 ERROR("fopen: %s", strerror(errno));
1006 gdImageDestroy(im);
1007 return(-1);
1010 /* Write the compressed image. */
1011 switch(config->format)
1013 case FORMAT_JPEG:
1014 MSG("Writing JPEG image to '%s'.", filename);
1015 gdImageJpeg(im, f, config->compression);
1016 break;
1017 case FORMAT_PNG:
1018 MSG("Writing PNG image to '%s'.", filename);
1019 gdImagePngEx(im, f, config->compression);
1020 break;
1023 if(f != stdout) fclose(f);
1025 gdImageDestroy(im);
1027 return(0);
1030 int fswc_exec(fswebcam_config_t *config, char *cmd)
1032 char *cmdline;
1033 FILE *p;
1035 cmdline = fswc_strduptime(cmd, config->start, config->gmt);
1036 if(!cmdline) return(-1);
1038 MSG("Executing '%s'...", cmdline);
1040 p = popen(cmdline, "r");
1041 free(cmdline);
1043 if(p)
1045 while(!feof(p))
1047 char *n;
1048 char line[1024];
1050 if(!fgets(line, 1024, p)) break;
1052 while((n = strchr(line, '\n'))) *n = '\0';
1053 MSG("%s", line);
1056 pclose(p);
1058 else
1060 ERROR("popen: %s", strerror(errno));
1061 return(-1);
1064 return(0);
1067 int fswc_grab(fswebcam_config_t *config)
1069 uint32_t frame;
1070 uint32_t x, y;
1071 avgbmp_t *abitmap, *pbitmap;
1072 gdImage *image, *original;
1073 uint8_t modified;
1074 src_t src;
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));
1105 if(!abitmap)
1107 ERROR("Out of memory.");
1108 return(-1);
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. */
1132 FILE *f;
1134 MSG("Dumping raw frame to '%s'...", config->dumpframe);
1136 f = fopen(config->dumpframe, "wb");
1137 if(!f) ERROR("fopen: %s", strerror(errno));
1138 else
1140 size_t l;
1141 l = fwrite(src.img, 1, src.length, f);
1142 fclose(f);
1146 /* Add frame to the average bitmap. */
1147 switch(src.palette)
1149 case SRC_PAL_PNG:
1150 fswc_add_image_png(&src, abitmap);
1151 break;
1152 case SRC_PAL_JPEG:
1153 case SRC_PAL_MJPEG:
1154 fswc_add_image_jpeg(&src, abitmap);
1155 break;
1156 case SRC_PAL_RGB32:
1157 fswc_add_image_rgb32(&src, abitmap);
1158 break;
1159 case SRC_PAL_BGR32:
1160 fswc_add_image_bgr32(&src, abitmap);
1161 break;
1162 case SRC_PAL_RGB24:
1163 fswc_add_image_rgb24(&src, abitmap);
1164 break;
1165 case SRC_PAL_BGR24:
1166 fswc_add_image_bgr24(&src, abitmap);
1167 break;
1168 case SRC_PAL_BAYER:
1169 case SRC_PAL_SGBRG8:
1170 fswc_add_image_bayer(&src, abitmap);
1171 break;
1172 case SRC_PAL_YUYV:
1173 case SRC_PAL_UYVY:
1174 fswc_add_image_yuyv(&src, abitmap);
1175 break;
1176 case SRC_PAL_YUV420P:
1177 fswc_add_image_yuv420p(&src, abitmap);
1178 break;
1179 case SRC_PAL_NV12MB:
1180 fswc_add_image_nv12mb(&src, abitmap);
1181 break;
1182 case SRC_PAL_RGB565:
1183 fswc_add_image_rgb565(&src, abitmap);
1184 break;
1185 case SRC_PAL_RGB555:
1186 fswc_add_image_rgb555(&src, abitmap);
1187 break;
1188 case SRC_PAL_GREY:
1189 fswc_add_image_grey(&src, abitmap);
1190 break;
1194 /* We are now finished with the capture card. */
1195 src_close(&src);
1197 /* Fail if no frames where captured. */
1198 if(!frame)
1200 ERROR("No frames captured.");
1201 free(abitmap);
1202 return(-1);
1205 HEAD("--- Processing captured image...");
1207 /* Copy the average bitmap image to a gdImage. */
1208 original = gdImageCreateTrueColor(config->width, config->height);
1209 if(!original)
1211 ERROR("Out of memory.");
1212 free(abitmap);
1213 return(-1);
1216 pbitmap = abitmap;
1217 for(y = 0; y < config->height; y++)
1218 for(x = 0; x < config->width; x++)
1220 int px = x;
1221 int py = y;
1222 int colour;
1224 colour = (*(pbitmap++) / config->frames) << 16;
1225 colour += (*(pbitmap++) / config->frames) << 8;
1226 colour += (*(pbitmap++) / config->frames);
1228 gdImageSetPixel(original, px, py, colour);
1231 free(abitmap);
1233 /* Make a copy of the original image. */
1234 image = fswc_gdImageDuplicate(original);
1235 if(!image)
1237 ERROR("Out of memory.");
1238 gdImageDestroy(image);
1239 return(-1);
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;
1258 config->shadow = 1;
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;
1269 modified = 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;
1277 switch(id)
1279 case 1: /* A non-option argument: a filename. */
1280 case OPT_SAVE:
1281 fswc_output(config, options, image);
1282 modified = 0;
1283 break;
1284 case OPT_EXEC:
1285 fswc_exec(config, options);
1286 break;
1287 case OPT_REVERT:
1288 modified = 1;
1289 gdImageDestroy(image);
1290 image = fswc_gdImageDuplicate(original);
1291 break;
1292 case OPT_FLIP:
1293 modified = 1;
1294 image = fx_flip(image, options);
1295 break;
1296 case OPT_CROP:
1297 modified = 1;
1298 image = fx_crop(image, options);
1299 break;
1300 case OPT_SCALE:
1301 modified = 1;
1302 image = fx_scale(image, options);
1303 break;
1304 case OPT_ROTATE:
1305 modified = 1;
1306 image = fx_rotate(image, options);
1307 break;
1308 case OPT_DEINTERLACE:
1309 modified = 1;
1310 image = fx_deinterlace(image, options);
1311 break;
1312 case OPT_INVERT:
1313 modified = 1;
1314 image = fx_invert(image, options);
1315 break;
1316 case OPT_GREYSCALE:
1317 modified = 1;
1318 image = fx_greyscale(image, options);
1319 break;
1320 case OPT_NO_BANNER:
1321 modified = 1;
1322 MSG("Disabling banner.");
1323 config->banner = NO_BANNER;
1324 break;
1325 case OPT_TOP_BANNER:
1326 modified = 1;
1327 MSG("Putting banner at the top.");
1328 config->banner = TOP_BANNER;
1329 break;
1330 case OPT_BOTTOM_BANNER:
1331 modified = 1;
1332 MSG("Putting banner at the bottom.");
1333 config->banner = BOTTOM_BANNER;
1334 break;
1335 case OPT_BG_COLOUR:
1336 modified = 1;
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);
1340 break;
1341 case OPT_BL_COLOUR:
1342 modified = 1;
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);
1346 break;
1347 case OPT_FG_COLOUR:
1348 modified = 1;
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);
1352 break;
1353 case OPT_FONT:
1354 modified = 1;
1355 MSG("Setting font to %s.", options);
1356 if(parse_font(options, &config->font, &config->fontsize))
1357 WARN("Bad font: %s", options);
1358 break;
1359 case OPT_NO_SHADOW:
1360 modified = 1;
1361 MSG("Disabling text shadow.");
1362 config->shadow = 0;
1363 break;
1364 case OPT_SHADOW:
1365 modified = 1;
1366 MSG("Enabling text shadow.");
1367 config->shadow = 1;
1368 break;
1369 case OPT_TITLE:
1370 modified = 1;
1371 MSG("Setting title \"%s\".", options);
1372 if(config->title) free(config->title);
1373 config->title = strdup(options);
1374 break;
1375 case OPT_NO_TITLE:
1376 modified = 1;
1377 MSG("Clearing title.");
1378 if(config->title) free(config->title);
1379 config->title = NULL;
1380 break;
1381 case OPT_SUBTITLE:
1382 modified = 1;
1383 MSG("Setting subtitle \"%s\".", options);
1384 if(config->subtitle) free(config->subtitle);
1385 config->subtitle = strdup(options);
1386 break;
1387 case OPT_NO_SUBTITLE:
1388 modified = 1;
1389 MSG("Clearing subtitle.");
1390 if(config->subtitle) free(config->subtitle);
1391 config->subtitle = NULL;
1392 break;
1393 case OPT_TIMESTAMP:
1394 modified = 1;
1395 MSG("Setting timestamp \"%s\".", options);
1396 if(config->timestamp) free(config->timestamp);
1397 config->timestamp = strdup(options);
1398 break;
1399 case OPT_NO_TIMESTAMP:
1400 modified = 1;
1401 MSG("Clearing timestamp.");
1402 if(config->timestamp) free(config->timestamp);
1403 config->timestamp = NULL;
1404 break;
1405 case OPT_INFO:
1406 modified = 1;
1407 MSG("Setting info text \"%s\".", options);
1408 if(config->info) free(config->info);
1409 config->info = strdup(options);
1410 break;
1411 case OPT_NO_INFO:
1412 modified = 1;
1413 MSG("Clearing info text.");
1414 if(config->info) free(config->info);
1415 config->info = NULL;
1416 break;
1417 case OPT_UNDERLAY:
1418 modified = 1;
1419 MSG("Setting underlay image: %s", options);
1420 if(config->underlay) free(config->underlay);
1421 config->underlay = strdup(options);
1422 break;
1423 case OPT_NO_UNDERLAY:
1424 modified = 1;
1425 MSG("Clearing underlay.");
1426 if(config->underlay) free(config->underlay);
1427 config->underlay = NULL;
1428 break;
1429 case OPT_OVERLAY:
1430 modified = 1;
1431 MSG("Setting overlay image: %s", options);
1432 if(config->overlay) free(config->overlay);
1433 config->overlay = strdup(options);
1434 break;
1435 case OPT_NO_OVERLAY:
1436 modified = 1;
1437 MSG("Clearing overlay image.");
1438 if(config->overlay) free(config->overlay);
1439 config->overlay = NULL;
1440 break;
1441 case OPT_JPEG:
1442 modified = 1;
1443 MSG("Setting output format to JPEG, quality %i", atoi(options));
1444 config->format = FORMAT_JPEG;
1445 config->compression = atoi(options);
1446 break;
1447 case OPT_PNG:
1448 modified = 1;
1449 MSG("Setting output format to PNG, quality %i", atoi(options));
1450 config->format = FORMAT_PNG;
1451 config->compression = atoi(options);
1452 break;
1456 gdImageDestroy(image);
1457 gdImageDestroy(original);
1459 if(modified) WARN("There are unsaved changes to the image.");
1461 return(0);
1464 int fswc_openlog(fswebcam_config_t *config)
1466 char *s;
1467 int r;
1469 /* Get the first part of the filename. */
1470 s = argdup(config->logfile, ":", 0, 0);
1471 if(!s)
1473 ERROR("Out of memory.");
1474 return(-1);
1477 if(!strcasecmp(s, "file"))
1479 free(s);
1481 s = argdup(config->logfile, ":", 1, 0);
1482 if(!s)
1484 ERROR("No log file was specified.");
1485 return(-1);
1488 else if(!strcasecmp(s, "syslog"))
1490 free(s);
1491 log_syslog(1);
1492 return(0);
1495 r = log_open(s);
1496 free(s);
1498 return(r);
1501 int fswc_background(fswebcam_config_t *config)
1503 pid_t pid, sid;
1505 /* Silence the output if not logging to a file. */
1506 if(!config->logfile) log_set_fd(-1);
1508 /* Fork into the background. */
1509 pid = fork();
1511 if(pid < 0)
1513 ERROR("Error going into the background.");
1514 ERROR("fork: %s", strerror(errno));
1515 return(-1);
1518 /* Is this the parent process? If so, end it. */
1519 if(pid > 0) exit(0);
1521 umask(0);
1523 /* Create a new SID for the child process. */
1524 sid = setsid();
1525 if(sid < 0)
1527 ERROR("Error going into the background.");
1528 ERROR("setsid: %s", strerror(errno));
1529 return(-1);
1532 close(STDIN_FILENO);
1533 close(STDOUT_FILENO);
1534 close(STDERR_FILENO);
1536 return(0);
1539 int fswc_savepid(fswebcam_config_t *config)
1541 FILE *fpid = fopen(config->pidfile, "wt");
1543 if(!fpid)
1545 ERROR("Error saving PID to file '%s'",config->pidfile);
1546 ERROR("fopen: %s", strerror(errno));
1548 return(-1);
1551 fprintf(fpid, "%i\n", getpid());
1552 fclose(fpid);
1554 return(0);
1557 int fswc_find_palette(char *name)
1559 int i;
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);
1574 return(-1);
1577 int fswc_usage()
1579 printf("Usage: fswebcam [<options>] <filename> [[<options>] <filename> ... ]\n"
1580 "\n"
1581 " Options:\n"
1582 "\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"
1637 "\n");
1639 return(0);
1642 int fswc_add_job(fswebcam_config_t *config, uint16_t id, char *options)
1644 fswebcam_job_t *job;
1645 void *n;
1647 job = malloc(sizeof(fswebcam_job_t));
1648 if(!job)
1650 ERROR("Out of memory.");
1651 return(-1);
1654 job->id = id;
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));
1660 if(!n)
1662 ERROR("Out of memory.");
1664 if(job->options) free(job->options);
1665 free(job);
1667 return(-1);
1670 config->job = n;
1672 /* Add the new job to the queue. */
1673 config->job[config->jobs++] = job;
1675 return(0);
1678 int fswc_free_jobs(fswebcam_config_t *config)
1680 int i;
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. */
1690 free(config->job);
1691 config->job = NULL;
1692 config->jobs = 0;
1694 return(0);
1697 int fswc_set_option(fswebcam_config_t *config, char *option)
1699 char *name, *value;
1701 if(!option) return(-1);
1703 name = strdup(option);
1704 if(!name)
1706 ERROR("Out of memory.");
1707 return(-1);
1710 value = strchr(name, '=');
1711 if(value)
1713 *(value++) = '\0';
1714 if(*value == '\0') value = NULL;
1717 src_set_option(&config->option, name, value);
1719 free(name);
1721 return(0);
1724 int fswc_getopt_file(fswc_getopt_t *s)
1726 char line[1024];
1727 char *arg, *val;
1728 struct option *opt;
1730 while(fgets(line, 1024, s->f))
1732 s->line++;
1733 strtrim(line, WHITESPACE);
1734 arg = argdup(line, WHITESPACE, 0, 0);
1736 if(!arg) continue;
1737 if(*arg == '#') continue;
1739 /* Find argument in the list. */
1740 opt = (struct option *) s->long_opts;
1741 while(opt->name)
1743 if(!strcasecmp(opt->name, arg)) break;
1744 opt++;
1747 if(!opt->name)
1749 ERROR("Unknown argument: %s", arg);
1750 WARN("%s,%i: %s", s->filename, s->line, line);
1751 free(arg);
1752 return(-2);
1755 if(opt->val == 'c')
1757 ERROR("You can't use config from a configuration file.");
1758 WARN("%s,%i: %s", s->filename, s->line, line);
1759 free(arg);
1760 return(-2);
1763 if(opt->has_arg)
1765 val = argdup(line, WHITESPACE, 1, 0);
1766 optarg = val;
1769 free(arg);
1771 return(opt->val);
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));
1780 return(-2);
1783 int fswc_getopt(fswc_getopt_t *s, int argc, char *argv[])
1785 int c;
1787 /* If a conf file is opened, read from that. */
1788 if(s->f)
1790 /* Read until we find an argument, error or EOF. */
1791 c = fswc_getopt_file(s);
1793 if(c < 0)
1795 /* EOF or error. */
1796 fclose(s->f);
1797 s->f = NULL;
1799 if(c == -2) return('!');
1801 else return(c);
1804 /* Read from the command line. */
1805 c = getopt_long(argc, argv, s->opts, s->long_opts, &s->opt_index);
1807 return(c);
1810 int fswc_getopts(fswebcam_config_t *config, int argc, char *argv[])
1812 int c;
1813 fswc_getopt_t s;
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},
1878 {0, 0, 0, 0}
1880 char *opts = "-qc:vl:bL:d:i:t:f:D:r:F:s:S:p:R";
1882 s.opts = opts;
1883 s.long_opts = long_opts;
1884 s.opt_index = 0;
1885 s.filename = NULL;
1886 s.f = NULL;
1887 s.line = 0;
1889 /* Set the defaults. */
1890 config->loop = 0;
1891 config->offset = 0;
1892 config->background = 0;
1893 config->pidfile = NULL;
1894 config->logfile = NULL;
1895 config->gmt = 0;
1896 config->start = 0;
1897 config->device = strdup("/dev/video0");
1898 config->input = NULL;
1899 config->tuner = 0;
1900 config->frequency = 0;
1901 config->delay = 0;
1902 config->use_read = 0;
1903 config->list = 0;
1904 config->width = 384;
1905 config->height = 288;
1906 config->fps = 0;
1907 config->frames = 1;
1908 config->skipframes = 0;
1909 config->palette = SRC_PAL_ANY;
1910 config->option = NULL;
1911 config->dumpframe = NULL;
1912 config->jobs = 0;
1913 config->job = NULL;
1915 /* Don't report errors. */
1916 opterr = 0;
1918 /* Reset getopt to ensure parsing begins at the first argument. */
1919 optind = 0;
1921 /* Parse the command line and any config files. */
1922 while((c = fswc_getopt(&s, argc, argv)) != -1)
1924 switch(c)
1926 case '?': fswc_usage(); /* Command line error. */
1927 case '!': return(-1); /* Conf file error. */
1928 case 'c':
1929 INFO("Reading configuration from '%s'...", optarg);
1930 s.f = fopen(optarg, "rt");
1931 if(!s.f)
1933 ERROR("fopen: %s", strerror(errno));
1934 return(-1);
1936 s.line = 0;
1937 free(s.filename);
1938 s.filename = strdup(optarg);
1939 break;
1940 case OPT_VERSION:
1941 fprintf(stderr, "fswebcam %s\n", PACKAGE_VERSION);
1942 return(-1);
1943 case 'q':
1944 log_quiet(-1);
1945 log_verbose(0);
1946 break;
1947 case 'v':
1948 log_quiet(0);
1949 log_verbose(-1);
1950 break;
1951 case 'l':
1952 config->loop = atol(optarg);
1953 break;
1954 case OPT_OFFSET:
1955 config->offset = atol(optarg);
1956 break;
1957 case 'b':
1958 config->background = -1;
1959 break;
1960 case OPT_PID:
1961 if(config->pidfile) free(config->pidfile);
1962 config->pidfile = strdup(optarg);
1963 break;
1964 case 'L':
1965 if(config->logfile) free(config->logfile);
1966 config->logfile = strdup(optarg);
1967 break;
1968 case 'd':
1969 if(config->device) free(config->device);
1970 config->device = strdup(optarg);
1971 break;
1972 case 'i':
1973 if(config->input) free(config->input);
1974 config->input = strdup(optarg);
1975 break;
1976 case OPT_LIST_INPUTS:
1977 config->list |= SRC_LIST_INPUTS;
1978 break;
1979 case 't':
1980 config->tuner = atoi(optarg);
1981 break;
1982 case OPT_LIST_TUNERS:
1983 config->list |= SRC_LIST_TUNERS;
1984 break;
1985 case 'f':
1986 config->frequency = atof(optarg) * 1000;
1987 break;
1988 case 'D':
1989 config->delay = atoi(optarg);
1990 break;
1991 case 'r':
1992 config->width = argtol(optarg, "x ", 0, 0, 10);
1993 config->height = argtol(optarg, "x ", 1, 0, 10);
1994 break;
1995 case OPT_FPS:
1996 config->fps = atoi(optarg);
1997 break;
1998 case 'F':
1999 config->frames = atoi(optarg);
2000 break;
2001 case 'S':
2002 config->skipframes = atoi(optarg);
2003 break;
2004 case 's':
2005 fswc_set_option(config, optarg);
2006 break;
2007 case OPT_LIST_CONTROLS:
2008 config->list |= SRC_LIST_CONTROLS;
2009 break;
2010 case 'p':
2011 config->palette = fswc_find_palette(optarg);
2012 if(config->palette == -1) return(-1);
2013 break;
2014 case 'R':
2015 config->use_read = -1;
2016 break;
2017 case OPT_LIST_FORMATS:
2018 config->list |= SRC_LIST_FORMATS;
2019 break;
2020 case OPT_GMT:
2021 config->gmt = -1;
2022 break;
2023 case OPT_DUMPFRAME:
2024 free(config->dumpframe);
2025 config->dumpframe = strdup(optarg);
2026 break;
2027 default:
2028 /* All other options are added to the job queue. */
2029 fswc_add_job(config, c, optarg);
2030 break;
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. */
2052 free(s.filename);
2054 return(0);
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);
2068 free(config->info);
2069 free(config->font);
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));
2079 return(0);
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);
2088 if(!config)
2090 WARN("Out of memory.");
2091 return(-1);
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);
2111 else
2113 /* Loop mode ... keep capturing images until terminated. */
2114 while(1 == 1)
2116 time_t capturetime = time(NULL);
2117 char timestamp[32];
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)
2136 usleep(250000);
2137 if(received_sighup)
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.");
2151 break;
2154 if(received_sigterm)
2156 MSG("Received TERM signal... exiting.");
2157 break;
2161 if(received_sigterm) break;
2163 /* Clear usr1 signal. */
2164 received_sigusr1 = 0;
2166 /* Capture the image. */
2167 fswc_grab(config);
2171 /* Close the log file. */
2172 if(config->logfile) log_close();
2174 /* Free all used memory. */
2175 fswc_free_config(config);
2176 free(config);
2178 return(0);