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