1 /*
2  * save pictures to disk (ppm,pgm,jpeg)
3  *
4  *  (c) 1998-2000 Gerd Knorr <kraxel@bytesex.org>
5  *
6  */
7 
8 #define NG_PRIVATE
9 #include "config.h"
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <ctype.h>
16 #include <errno.h>
17 #include <time.h>
18 #include <fcntl.h>
19 #include <jpeglib.h>
20 #include <pthread.h>
21 #include <inttypes.h>
22 #include <sys/types.h>
23 #include <sys/param.h>
24 #include "byteswap.h"
25 
26 #include "grab-ng.h"
27 #include "writefile.h"
28 
29 /* ---------------------------------------------------------------------- */
30 
31 /*
32  * count up the latest block of digits in the passed string
33  * (used for filename numbering
34  */
35 int
patch_up(char * name)36 patch_up(char *name)
37 {
38     char *ptr;
39 
40     for (ptr = name+strlen(name); ptr >= name; ptr--)
41 	if (isdigit(*ptr))
42 	    break;
43     if (ptr < name)
44 	return 0;
45     while (*ptr == '9' && ptr >= name)
46 	*(ptr--) = '0';
47     if (ptr < name)
48 	return 0;
49     if (isdigit(*ptr)) {
50 	(*ptr)++;
51 	return 1;
52     }
53     return 0;
54 }
55 
56 char*
snap_filename(char * base,char * channel,char * ext)57 snap_filename(char *base, char *channel, char *ext)
58 {
59     static time_t last = 0;
60     static int count = 0;
61     static char *filename = NULL;
62 
63     time_t now;
64     struct tm* tm;
65     char timestamp[32];
66 
67     time(&now);
68     tm = localtime(&now);
69 
70     if (last != now)
71 	count = 0;
72     last = now;
73     count++;
74 
75     if (filename != NULL)
76 	free(filename);
77     filename = malloc(strlen(base)+strlen(channel)+strlen(ext)+32);
78 
79     strftime(timestamp,31,"%Y%m%d-%H%M%S",tm);
80     sprintf(filename,"%s-%s-%s-%d.%s",
81 	    base,channel,timestamp,count,ext);
82     return filename;
83 }
84 
85 /* ---------------------------------------------------------------------- */
86 
do_write_jpeg(FILE * fp,struct ng_video_buf * buf,int quality,int gray)87 static int do_write_jpeg(FILE *fp, struct ng_video_buf *buf,
88 			 int quality, int gray)
89 {
90     struct jpeg_compress_struct cinfo;
91     struct jpeg_error_mgr jerr;
92     unsigned int i;
93     unsigned char *line;
94     int line_length;
95 
96     cinfo.err = jpeg_std_error(&jerr);
97     jpeg_create_compress(&cinfo);
98     jpeg_stdio_dest(&cinfo, fp);
99     cinfo.image_width  = buf->fmt.width;
100     cinfo.image_height = buf->fmt.height;
101     cinfo.input_components = gray ? 1: 3;
102     cinfo.in_color_space = gray ? JCS_GRAYSCALE: JCS_RGB;
103     jpeg_set_defaults(&cinfo);
104     jpeg_set_quality(&cinfo, quality, TRUE);
105     jpeg_start_compress(&cinfo, TRUE);
106 
107     line_length = gray ? buf->fmt.width : buf->fmt.width * 3;
108     for (i = 0, line = buf->data; i < buf->fmt.height;
109 	 i++, line += line_length)
110 	jpeg_write_scanlines(&cinfo, &line, 1);
111 
112     jpeg_finish_compress(&(cinfo));
113     jpeg_destroy_compress(&(cinfo));
114     fclose(fp);
115 
116     return 0;
117 }
118 
write_jpeg(char * filename,struct ng_video_buf * buf,int quality,int gray)119 int write_jpeg(char *filename, struct ng_video_buf *buf,
120 	       int quality, int gray)
121 {
122     FILE *fp;
123 
124     if (NULL == (fp = fopen(filename,"w"))) {
125 	fprintf(stderr,"grab: can't open %s: %s\n",filename,strerror(errno));
126 	return -1;
127     }
128     return do_write_jpeg(fp,buf,quality,gray);
129 }
130 
131 #if 0
132 int write_jpeg_fd(int fd, struct ng_video_buf *buf,
133 		  int quality, int gray)
134 {
135     FILE *fp;
136 
137     if (NULL == (fp = fdopen(fd,"w"))) {
138 	fprintf(stderr,"grab: can't fdopen(%d): %s\n",fd,strerror(errno));
139 	return -1;
140     }
141     return do_write_jpeg(fp,buf,quality,gray);
142 }
143 #endif
144 
write_ppm(char * filename,struct ng_video_buf * buf)145 int write_ppm(char *filename, struct ng_video_buf *buf)
146 {
147     FILE *fp;
148 
149     if (NULL == (fp = fopen(filename,"w"))) {
150 	fprintf(stderr,"grab: can't open %s: %s\n",filename,strerror(errno));
151 	return -1;
152     }
153     fprintf(fp,"P6\n%d %d\n255\n",
154 	    buf->fmt.width,buf->fmt.height);
155     fwrite(buf->data, buf->fmt.height, 3*buf->fmt.width,fp);
156     fclose(fp);
157 
158     return 0;
159 }
160 
write_pgm(char * filename,struct ng_video_buf * buf)161 int write_pgm(char *filename, struct ng_video_buf *buf)
162 {
163     FILE *fp;
164 
165     if (NULL == (fp = fopen(filename,"w"))) {
166 	fprintf(stderr,"grab: can't open %s: %s\n",filename,strerror(errno));
167 	return -1;
168     }
169     fprintf(fp,"P5\n%d %d\n255\n",
170 	    buf->fmt.width, buf->fmt.height);
171     fwrite(buf->data, buf->fmt.height, buf->fmt.width, fp);
172     fclose(fp);
173 
174     return 0;
175 }
176 
177 /* ---------------------------------------------------------------------- */
178 /* *.wav I/O stolen from cdda2wav                                         */
179 
180 /* Copyright (C) by Heiko Eissfeldt */
181 
182 typedef uint8_t   BYTE;
183 typedef uint16_t  WORD;
184 typedef uint32_t  DWORD;
185 typedef uint32_t  FOURCC;	/* a four character code */
186 
187 /* flags for 'wFormatTag' field of WAVEFORMAT */
188 #define WAVE_FORMAT_PCM 1
189 
190 /* MMIO macros */
191 #define mmioFOURCC(ch0, ch1, ch2, ch3) \
192   ((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \
193   ((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24))
194 
195 #define FOURCC_RIFF	mmioFOURCC ('R', 'I', 'F', 'F')
196 #define FOURCC_LIST	mmioFOURCC ('L', 'I', 'S', 'T')
197 #define FOURCC_WAVE	mmioFOURCC ('W', 'A', 'V', 'E')
198 #define FOURCC_FMT	mmioFOURCC ('f', 'm', 't', ' ')
199 #define FOURCC_DATA	mmioFOURCC ('d', 'a', 't', 'a')
200 
201 typedef struct CHUNKHDR {
202     FOURCC ckid;		/* chunk ID */
203     DWORD dwSize; 		/* chunk size */
204 } CHUNKHDR;
205 
206 /* simplified Header for standard WAV files */
207 typedef struct WAVEHDR {
208     CHUNKHDR chkRiff;
209     FOURCC fccWave;
210     CHUNKHDR chkFmt;
211     WORD wFormatTag;	   /* format type */
212     WORD nChannels;	   /* number of channels (i.e. mono, stereo, etc.) */
213     DWORD nSamplesPerSec;  /* sample rate */
214     DWORD nAvgBytesPerSec; /* for buffer estimation */
215     WORD nBlockAlign;	   /* block size of data */
216     WORD wBitsPerSample;
217     CHUNKHDR chkData;
218 } WAVEHDR;
219 
220 #if BYTE_ORDER == BIG_ENDIAN
221 # define cpu_to_le32(x) SWAP4((x))
222 # define cpu_to_le16(x) SWAP2((x))
223 # define le32_to_cpu(x) SWAP4((x))
224 # define le16_to_cpu(x) SWAP2((x))
225 #else
226 # define cpu_to_le32(x) (x)
227 # define cpu_to_le16(x) (x)
228 # define le32_to_cpu(x) (x)
229 # define le16_to_cpu(x) (x)
230 #endif
231 
232 static void
wav_init_header(WAVEHDR * fileheader,struct ng_audio_fmt * audio)233 wav_init_header(WAVEHDR *fileheader, struct ng_audio_fmt *audio)
234 {
235     /* stolen from cdda2wav */
236     int nBitsPerSample = ng_afmt_to_bits[audio->fmtid];
237     int channels       = ng_afmt_to_channels[audio->fmtid];
238     int rate           = audio->rate;
239 
240     unsigned long nBlockAlign = channels * ((nBitsPerSample + 7) / 8);
241     unsigned long nAvgBytesPerSec = nBlockAlign * rate;
242     unsigned long temp = /* data length */ 0 +
243 	sizeof(WAVEHDR) - sizeof(CHUNKHDR);
244 
245     fileheader->chkRiff.ckid    = cpu_to_le32(FOURCC_RIFF);
246     fileheader->fccWave         = cpu_to_le32(FOURCC_WAVE);
247     fileheader->chkFmt.ckid     = cpu_to_le32(FOURCC_FMT);
248     fileheader->chkFmt.dwSize   = cpu_to_le32(16);
249     fileheader->wFormatTag      = cpu_to_le16(WAVE_FORMAT_PCM);
250     fileheader->nChannels       = cpu_to_le16(channels);
251     fileheader->nSamplesPerSec  = cpu_to_le32(rate);
252     fileheader->nAvgBytesPerSec = cpu_to_le32(nAvgBytesPerSec);
253     fileheader->nBlockAlign     = cpu_to_le16(nBlockAlign);
254     fileheader->wBitsPerSample  = cpu_to_le16(nBitsPerSample);
255     fileheader->chkData.ckid    = cpu_to_le32(FOURCC_DATA);
256     fileheader->chkRiff.dwSize  = cpu_to_le32(temp);
257     fileheader->chkData.dwSize  = cpu_to_le32(0 /* data length */);
258 }
259 
260 static void
wav_start_write(int fd,WAVEHDR * fileheader,struct ng_audio_fmt * audio)261 wav_start_write(int fd, WAVEHDR *fileheader, struct ng_audio_fmt *audio)
262 {
263     wav_init_header(fileheader,audio);
264     write(fd,fileheader,sizeof(WAVEHDR));
265 }
266 
267 static void
wav_stop_write(int fd,WAVEHDR * fileheader,int wav_size)268 wav_stop_write(int fd, WAVEHDR *fileheader, int wav_size)
269 {
270     unsigned long temp = wav_size + sizeof(WAVEHDR) - sizeof(CHUNKHDR);
271 
272     fileheader->chkRiff.dwSize = cpu_to_le32(temp);
273     fileheader->chkData.dwSize = cpu_to_le32(wav_size);
274     lseek(fd,0,SEEK_SET);
275     write(fd,fileheader,sizeof(WAVEHDR));
276 }
277 
278 /* ---------------------------------------------------------------------- */
279 
280 struct files_handle {
281     /* file name */
282     char   file[MAXPATHLEN];
283 
284     /* format */
285     struct ng_video_fmt video;
286     struct ng_audio_fmt audio;
287 
288     /* *.wav file */
289     int      wav_fd;
290     WAVEHDR  wav_header;
291     int      wav_size;
292 
293     /* misc */
294     int      gotcha;
295 };
296 
297 static void*
files_open(char * filesname,char * audioname,struct ng_video_fmt * video,const void * priv_video,int fps,struct ng_audio_fmt * audio,const void * priv_audio)298 files_open(char *filesname, char *audioname,
299 	   struct ng_video_fmt *video, const void *priv_video, int fps,
300 	   struct ng_audio_fmt *audio, const void *priv_audio)
301 {
302     struct files_handle *h;
303 
304     if (video->fmtid != VIDEO_NONE && NULL == filesname)
305 	return NULL;
306     if (NULL == (h = malloc(sizeof(*h))))
307 	return NULL;
308 
309     /* init */
310     memset(h,0,sizeof(*h));
311     h->video         = *video;
312     h->audio         = *audio;
313     if (filesname)
314 	strcpy(h->file,filesname);
315 
316     if (h->audio.fmtid != AUDIO_NONE) {
317 	h->wav_fd = open(audioname, O_CREAT | O_RDWR | O_TRUNC, 0666);
318 	if (-1 == h->wav_fd) {
319 	    fprintf(stderr,"open %s: %s\n",audioname,strerror(errno));
320 	    free(h);
321 	    return NULL;
322 	}
323 	wav_start_write(h->wav_fd,&h->wav_header,&h->audio);
324     }
325 
326     return h;
327 }
328 
329 static int
files_video(void * handle,struct ng_video_buf * buf)330 files_video(void *handle, struct ng_video_buf *buf)
331 {
332     struct files_handle *h = handle;
333     int rc = -1;
334     FILE *fp;
335 
336     if (h->gotcha) {
337 	fprintf(stderr,"Oops: can't count up file names any more\n");
338 	return -1;
339     }
340 
341     switch (h->video.fmtid) {
342     case VIDEO_RGB24:
343 	rc = write_ppm(h->file, buf);
344 	break;
345     case VIDEO_GRAY:
346 	rc = write_pgm(h->file, buf);
347 	break;
348     case VIDEO_JPEG:
349 	if (NULL == (fp = fopen(h->file,"w"))) {
350 	    fprintf(stderr,"grab: can't open %s: %s\n",h->file,strerror(errno));
351 	    rc = -1;
352 	} else {
353 	    fwrite(buf->data,buf->size,1,fp);
354 	    fclose(fp);
355 	    rc = 0;
356 	}
357     }
358     if (1 != patch_up(h->file))
359 	h->gotcha = 1;
360     return rc;
361 }
362 
363 static int
files_audio(void * handle,struct ng_audio_buf * buf)364 files_audio(void *handle, struct ng_audio_buf *buf)
365 {
366     struct files_handle *h = handle;
367     if (buf->size != write(h->wav_fd,buf->data,buf->size))
368 	return -1;
369     h->wav_size += buf->size;
370     return 0;
371 }
372 
373 static int
files_close(void * handle)374 files_close(void *handle)
375 {
376     struct files_handle *h = handle;
377 
378     if (h->audio.fmtid != AUDIO_NONE) {
379 	wav_stop_write(h->wav_fd,&h->wav_header,h->wav_size);
380 	close(h->wav_fd);
381     }
382     free(h);
383     return 0;
384 }
385 
386 /* ---------------------------------------------------------------------- */
387 
388 struct raw_priv {
389     int      yuv4mpeg;
390 };
391 
392 struct raw_handle {
393     /* format */
394     struct ng_video_fmt video;
395     struct ng_audio_fmt audio;
396     const struct raw_priv *vpriv;
397 
398     /* video file*/
399     int      fd;
400 
401     /* *.wav file */
402     int      wav_fd;
403     WAVEHDR  wav_header;
404     int      wav_size;
405 };
406 
407 static void*
raw_open(char * videoname,char * audioname,struct ng_video_fmt * video,const void * priv_video,int fps,struct ng_audio_fmt * audio,const void * priv_audio)408 raw_open(char *videoname, char *audioname,
409 	 struct ng_video_fmt *video, const void *priv_video, int fps,
410 	 struct ng_audio_fmt *audio, const void *priv_audio)
411 {
412     struct raw_handle *h;
413     int frame_rate_code = 0;
414     int frame_rate_mul  = fps;
415     int frame_rate_div  = 1000;
416 
417     if (NULL == (h = malloc(sizeof(*h))))
418 	return NULL;
419 
420     /* init */
421     memset(h,0,sizeof(*h));
422     h->video         = *video;
423     h->audio         = *audio;
424     h->vpriv         = priv_video;
425 
426     /* audio */
427     if (h->audio.fmtid != AUDIO_NONE) {
428 	h->wav_fd = open(audioname, O_CREAT | O_RDWR | O_TRUNC, 0666);
429 	if (-1 == h->wav_fd) {
430 	    fprintf(stderr,"open %s: %s\n",audioname,strerror(errno));
431 	    free(h);
432 	    return NULL;
433 	}
434 	wav_start_write(h->wav_fd,&h->wav_header,&h->audio);
435     }
436 
437     /* video */
438     if (h->video.fmtid != VIDEO_NONE) {
439 	if (h->vpriv && h->vpriv->yuv4mpeg) {
440 	    switch (fps) {
441 	    case 23976:  frame_rate_code = 1;          /* 24000 / 1001 */
442 			 frame_rate_mul  = 24000;
443 			 frame_rate_div  = 1001;
444 			 break;
445 	    case 29970:  frame_rate_code = 4;          /* 30000 / 1001 */
446 			 frame_rate_mul  = 30000;
447 			 frame_rate_div  = 1001;
448 			 break;
449 	    case 59940:  frame_rate_code = 7;          /* 60000 / 1001 */
450 			 frame_rate_mul  = 60000;
451 			 frame_rate_div  = 1001;
452 			 break;
453 	    case 24000:  frame_rate_code = 2; break;
454 	    case 25000:  frame_rate_code = 3; break;
455 	    case 30000:  frame_rate_code = 5; break;
456 	    case 50000:  frame_rate_code = 6; break;
457 	    case 60000:  frame_rate_code = 8; break;
458 	    default:
459 		fprintf(stderr,"illegal frame rate\n");
460 		free(h);
461 		return NULL;
462 	    }
463 	}
464 	if (NULL != videoname) {
465 	    h->fd = open(videoname, O_CREAT | O_RDWR | O_TRUNC, 0666);
466 	    if (-1 == h->fd) {
467 		fprintf(stderr,"open %s: %s\n",videoname,strerror(errno));
468 		if (h->wav_fd)
469 		    close(h->wav_fd);
470 		free(h);
471 		return NULL;
472 	    }
473 	} else {
474 	    h->fd = 1; /* use stdout */
475 	}
476 	if (h->vpriv && h->vpriv->yuv4mpeg) {
477 	    char header[64];
478 	    switch (h->vpriv->yuv4mpeg) {
479 	    case 1:
480 		sprintf(header, "YUV4MPEG %d %d %d\n",
481 			h->video.width, h->video.height,frame_rate_code);
482 		break;
483 	    case 2:
484 		sprintf(header, "YUV4MPEG2 W%d H%d F%d:%d\n",
485 			h->video.width, h->video.height,
486 			frame_rate_mul, frame_rate_div);
487 		break;
488 	    }
489 	    write(h->fd, header, strlen(header));
490 	}
491     }
492 
493     return h;
494 }
495 
496 static int
raw_video(void * handle,struct ng_video_buf * buf)497 raw_video(void *handle, struct ng_video_buf *buf)
498 {
499     struct raw_handle *h = handle;
500 
501     if (h->vpriv && h->vpriv->yuv4mpeg)
502 	switch (h->vpriv->yuv4mpeg) {
503 	case 1:
504 	   if (6 != write(h->fd, "FRAME\n", 6))
505 		return -1;
506 	   break;
507 	case 2:
508 	   if (7 != write(h->fd, "FRAME \n", 7))
509 		return -1;
510 	   break;
511 	}
512 
513     if (buf->size != write(h->fd,buf->data,buf->size))
514 	return -1;
515     return 0;
516 }
517 
518 static int
raw_audio(void * handle,struct ng_audio_buf * buf)519 raw_audio(void *handle, struct ng_audio_buf *buf)
520 {
521     struct raw_handle *h = handle;
522     if (buf->size != write(h->wav_fd,buf->data,buf->size))
523 	return -1;
524     h->wav_size += buf->size;
525     return 0;
526 }
527 
528 static int
raw_close(void * handle)529 raw_close(void *handle)
530 {
531     struct raw_handle *h = handle;
532 
533     if (h->audio.fmtid != AUDIO_NONE) {
534 	wav_stop_write(h->wav_fd,&h->wav_header,h->wav_size);
535 	close(h->wav_fd);
536     }
537     if (h->video.fmtid != VIDEO_NONE && 1 != h->fd)
538 	close(h->fd);
539     free(h);
540     return 0;
541 }
542 
543 /* ----------------------------------------------------------------------- */
544 /* data structures describing our capabilities                             */
545 
546 static const struct ng_format_list files_vformats[] = {
547     {
548 	.name =  "ppm",
549 	.ext =   "ppm",
550 	.fmtid = VIDEO_RGB24,
551     },{
552 	.name =  "pgm",
553 	.ext =   "pgm",
554 	.fmtid = VIDEO_GRAY,
555     },{
556 	.name =  "jpeg",
557 	.ext =   "jpeg",
558 	.fmtid = VIDEO_JPEG,
559     },{
560 	/* EOF */
561     }
562 };
563 
564 static struct raw_priv yuv4mpeg = {
565     .yuv4mpeg = 1
566 };
567 
568 static struct raw_priv yuv4mpeg2 = {
569     .yuv4mpeg = 2
570 };
571 
572 static const struct ng_format_list raw_vformats[] = {
573     {
574 	.name =  "rgb",
575 	.ext =   "raw",
576 	.fmtid = VIDEO_RGB24,
577     },{
578 	.name =  "gray",
579 	.ext =   "raw",
580 	.fmtid = VIDEO_GRAY,
581     },{
582 	.name =  "422",
583 	.ext =   "raw",
584 	.fmtid = VIDEO_YUYV,
585     },{
586 	.name =  "422p",
587 	.ext =   "raw",
588 	.fmtid = VIDEO_YUV422P,
589     },{
590 	.name =  "4mpeg",
591 	.desc=   "yuv4mpeg (mpeg2enc >= 1.6)",
592 	.ext =   "yuv",
593 	.fmtid = VIDEO_YUV420P,
594 	.priv =  &yuv4mpeg2,
595     },{
596 	.name =  "4mpeg-o",
597 	.desc =  "yuv4mpeg (old mpeg2enc)",
598 	.ext =   "yuv",
599 	.fmtid = VIDEO_YUV420P,
600 	.priv =  &yuv4mpeg,
601     },{
602 	/* EOF */
603     }
604 };
605 
606 static const struct ng_format_list wav_aformats[] = {
607     {
608 	.name =  "mono8",
609 	.ext =   "wav",
610 	.fmtid = AUDIO_U8_MONO,
611     },{
612 	.name =  "mono16",
613 	.ext =   "wav",
614 	.fmtid = AUDIO_S16_LE_MONO,
615     },{
616 	.name =  "stereo",
617 	.ext =   "wav",
618 	.fmtid = AUDIO_S16_LE_STEREO,
619     },{
620 	/* EOF */
621     }
622 };
623 
624 struct ng_writer files_writer = {
625     .name =      "files",
626     .desc =      "multiple image files",
627     .video =     files_vformats,
628     .audio =     wav_aformats,
629     .wr_open =   files_open,
630     .wr_video =  files_video,
631     .wr_audio =  files_audio,
632     .wr_close =  files_close,
633 };
634 
635 struct ng_writer raw_writer = {
636     .name =      "raw",
637     .desc =      "single file, raw video data",
638     .video =     raw_vformats,
639     .audio =     wav_aformats,
640     .wr_open =   raw_open,
641     .wr_video =  raw_video,
642     .wr_audio =  raw_audio,
643     .wr_close =  raw_close,
644 };
645 
646 /* ------------------------------------------------------------------- */
647 
648 void
ng_writefile_init(void)649 ng_writefile_init(void)
650 {
651     ng_writer_register(NG_PLUGIN_MAGIC,"built-in",&files_writer);
652     ng_writer_register(NG_PLUGIN_MAGIC,"built-in",&raw_writer);
653 }
654