1 /*
2  * batch_mode.c
3  *
4  * Routines to handle batch mode operation.
5  *
6  * (C) 1998 Randall Hopper
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are
10  * met: 1. Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer. 2.
12  * Redistributions in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
20  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  *
28  */
29 
30 /*      ******************** Include Files                ************** */
31 
32 #include <stdio.h>
33 #include <fcntl.h>
34 #include <string.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include "tvtypes.h"
38 #include "rawvideo.h"
39 #include "imgsav.h"
40 #include "glob.h"
41 
42 /*      ******************** Local defines                ************** */
43 
44 /*#define DEBUGGING*/
45 
46 #define FRAME_USEC(fps)    (fps <= 0.0 ? 0.0 : (1000000L /(fps)))
47 
48 typedef struct {
49           char         stream_input[4][80];
50           TV_INT32     stream_num_input;
51           TV_INT32     stream_fps;
52           char        *frame_fmt;
53           char        *video_target;
54           char        *audio_target;
55 } TV_BATCH_PARM;
56 
57 
58 /*      ******************** Private variables            ************** */
59 
60 static TV_BOOL Got_signal = FALSE;
61 
62 /*      ******************** Forward declarations         ************** */
63 /*      ******************** Function Definitions         ************** */
64 
65 /*  TVIntrHdlr - Called when we receive interrupts.  Queue the last one  */
66 /*    and process it next time we get back to the work proc.             */
67 
68 /**@BEGINFUNC**************************************************************
69 
70     Prototype  : static void TVBatchIntrHdlr(
71                       int sig )
72 
73     Purpose    : Called when we receive an INT or TERM signal.  We
74                  just set a state variable here, and check it during
75                  long processing.
76 
77     Programmer : 21-May-98  Randall Hopper
78 
79     Parameters : sig - I: signal that occurred
80 
81     Returns    : None.
82 
83     Globals    : None.
84 
85  **@ENDFUNC*****************************************************************/
86 
TVBatchIntrHdlr(int sig)87 static void TVBatchIntrHdlr( int sig )
88 {
89     Got_signal = TRUE;
90     exit(1);
91 }
92 
93 
94 /**@BEGINFUNC**************************************************************
95 
96     Prototype  : static void DoStreamVideo(
97                       TV_BATCH_PARM *parm )
98 
99     Purpose    : Called in batch mode to stream video in a particular format
100                  to stdout.
101 
102     Programmer : 15-Jan-98  Randall Hopper
103 
104     Parameters : parm - I: batch parameters
105 
106     Returns    : None.
107 
108     Globals    : None.
109 
110  **@ENDFUNC*****************************************************************/
111 
DoStreamVideo(TV_BATCH_PARM * parm)112 static void DoStreamVideo( TV_BATCH_PARM *parm )
113 {
114     TV_STILL_FMT  fmt;
115     char         *raw_files[4] = { parm->stream_input[0],
116                                    parm->stream_input[1],
117                                    parm->stream_input[2],
118                                    parm->stream_input[3] },
119                  *frame_ext = NULL;
120     TV_RAW_VIDEO_FILE   *rf;
121     TV_RAW_IMAGE_HEADER  head;
122     TV_RAW_IMAGE         img;
123     TV_RAW_SOUND         snd;
124     TV_BOOL              eof,
125                          video2stdout,
126                          audio2stdout;
127     size_t               bytes;
128     TV_INT32             write_cnt,
129                          i,
130                          frame_no,
131                          capture_time,
132                          mpeg_time,
133                          delta,
134                          mpeg_frame_time = FRAME_USEC( parm->stream_fps );
135     int                  aud_fd = -1;
136 
137     /*  Parse parms  */
138     if (( parm->frame_fmt == NULL ) || ( parm->stream_num_input == 0 )) {
139         fprintf( stderr, "Missing streamformat and/or streaminput.\n" );
140         exit(1);
141     }
142     if (( parm->video_target == NULL ) && ( parm->audio_target == NULL )) {
143         fprintf( stderr, "Neither videotarget or audiotarget specified\n" );
144         exit(1);
145     }
146 
147     video2stdout = (parm->video_target && STREQ( parm->video_target, "-" ));
148     audio2stdout = (parm->audio_target && STREQ( parm->audio_target, "-" ));
149 
150     if ( video2stdout && audio2stdout ) {
151         fprintf( stderr, "Video and audio can't both be sent to stdout\n" );
152         exit(1);
153     }
154 
155     if ( STREQ( parm->frame_fmt, "TIFF" ) )
156         fmt = TV_STILL_FMT_TIFF;
157     else if ( STREQ( parm->frame_fmt, "PPM" ) )
158         fmt = TV_STILL_FMT_PPM;
159     else if ( STREQ( parm->frame_fmt, "YUV" ) )
160         fmt = TV_STILL_FMT_YUV;
161     else {
162         fprintf( stderr, "Bad frameformat: %s\n", parm->frame_fmt );
163         exit(1);
164     }
165 
166     /*  Sanity check target with frame format  */
167     if ( ( fmt != TV_STILL_FMT_PPM ) && ( fmt != TV_STILL_FMT_YUV ) &&
168          video2stdout ) {
169         fprintf( stderr, "Can't send this frame format to stdout: %s\n",
170                  parm->frame_fmt );
171         exit(1);
172     }
173 
174     /*  Derive image file extension  */
175     if ( parm->video_target && !video2stdout )
176         switch ( fmt ) {
177             case TV_STILL_FMT_TIFF : frame_ext = "tif";  break;
178             case TV_STILL_FMT_PPM  : frame_ext = "ppm";  break;
179             case TV_STILL_FMT_YUV  : frame_ext = "yuv";  break;
180             default: abort();
181         }
182 
183     /*  Open the raw input file(s)  */
184     if ( !TVRAWVIDEOOpen( raw_files, parm->stream_num_input, TRUE, &rf ) ) {
185         fprintf( stderr, "Failed to open input file(s)\n" );
186         exit(1);
187     }
188 
189     /*  Prepare the audio output filedesc  */
190     if ( parm->audio_target )
191         if ( audio2stdout )
192             aud_fd = 1;
193         else
194             if ( (aud_fd = open( parm->audio_target, O_CREAT|O_WRONLY|O_TRUNC,
195                                  0666 )) < 0 ) {
196                 fprintf( stderr, "Failed to open output raw audio file: %s\n",
197                          parm->audio_target );
198                 exit(1);
199             }
200 
201     /*  Read header  */
202     if ( !TVRAWVIDEOHeaderRead( rf, &img, &snd, &eof ) ) {
203         fprintf( stderr, "Failed reading raw capture file\n" );
204         exit(1);
205     }
206 
207     /*  If no frames, we're done  */
208     if ( eof )
209         return;
210 
211     /*  Sanity check raw data with save type  */
212     if ( ((( fmt == TV_STILL_FMT_TIFF ) || ( fmt == TV_STILL_FMT_PPM )) &&
213           ( img.pix_geom.type != TV_PIXELTYPE_RGB )) ||
214          (( fmt == TV_STILL_FMT_YUV ) &&
215           ( img.pix_geom.type != TV_PIXELTYPE_YUV )) ) {
216         fprintf( stderr, "Sorry, that stream format is not supported with "
217                          "the type of this raw capture data.\n" );
218         exit(1);
219     }
220 
221     /*  Allocate image frame buffer  */
222     bytes = TVRAWVIDEOCalcImageSize( &img );
223     if ( (img.buf = malloc( bytes )) == NULL )
224         TVUTILOutOfMemory();
225 
226     frame_no = 1;
227     capture_time = mpeg_time = 0;
228     while ( !eof ) {
229         char img_last [ MAXPATHLEN ];
230 
231         if ( !TVRAWVIDEOImageRead( rf, &head, &img, &snd, &eof ) ) {
232             fprintf( stderr, "Failed reading raw image file\n" );
233             exit(1);
234         }
235         if ( eof )
236             break;
237 
238         /*  Write any audio stored with the frame to the RAW audio file  */
239         if ( snd.bytes ) {
240             if ( parm->audio_target )
241                 write( aud_fd, snd.buf, snd.bytes );
242             free( snd.buf );
243             snd.buf = NULL;
244         }
245 
246 #ifdef DEBUGGING
247         fprintf( stderr, "FRAME_USEC = %ld\n", mpeg_frame_time );
248 #endif
249         if ( parm->video_target ) {
250             if ( parm->stream_fps <= 0 )
251                write_cnt = 1;
252             else {
253 
254                /*  Figure out whether to drop frame or write multiple times  */
255                capture_time += head.delay;
256                delta = capture_time - mpeg_time;
257 
258                if ( delta < -0.5 * mpeg_frame_time )
259                    /*  We're ahead of the clock; drop this frame  */
260                    write_cnt = 0;
261 
262                else
263                    for ( write_cnt = 0; delta >= -0.5 * mpeg_frame_time;
264                          write_cnt++ ) {
265                        mpeg_time += mpeg_frame_time;
266                        delta      = capture_time - mpeg_time;
267                    }
268 #ifdef DEBUGGING
269                fprintf( stderr, "-- delay =%6ld, capture =%7ld, mpeg =%7ld, "
270                                 "delta =%7ld, write_cnt = %ld\n",
271                         head.delay, capture_time, mpeg_time, delta,
272                         write_cnt );
273 #else
274                /*  Avoid overflow  */
275                mpeg_time    -= capture_time;
276                capture_time  = 0;
277 #endif
278 
279                /*  FIXME: Rezero action/mpeg after each frame  */
280             }
281 
282             /*  Consider installing write buffer on stdout  */
283             for ( i = 0; i < write_cnt; i++ ) {
284                 char  img_fname[ MAXPATHLEN ];
285 
286                 if ( video2stdout )
287                     TVIMGSAVDoSave( NULL, fmt, &img );
288                 else {
289                     char suffix[80];
290 
291                     sprintf( suffix, ".%.5ld.%s", frame_no++, frame_ext );
292 
293                     sprintf( img_fname, parm->video_target, suffix );
294                     if ( i == 0 ) {
295                         TVIMGSAVDoSave( img_fname, fmt, &img );
296                         strcpy( img_last, img_fname );
297                     }
298                     else
299                         link( img_last, img_fname );
300                 }
301             }
302         }
303     }
304 
305     TVRAWVIDEOClose( &rf );
306     free( img.buf );
307 
308     if ( parm->audio_target && !audio2stdout )
309         close( aud_fd );
310 }
311 
312 
313 /**@BEGINFUNC**************************************************************
314 
315     Prototype  : TV_BOOL DoBatchMode(
316                       int argc,
317                       char *argv[] )
318 
319     Purpose    : This mode is called before we do anything GUI related to
320                  see if this is a Batch Mode run, and if so, to dispatch
321                  the appropriate behavior.
322 
323     Programmer : 15-Jan-98  Randall Hopper
324 
325     Parameters : argc - main() argc
326                  argv - main() argv
327 
328     Returns    : TRUE  - This was a batch mode run, and we're done with it
329                  FALSE - This isn't a Batch Mode run; nothing done yet
330 
331     Globals    : Batch_parm
332 
333  **@ENDFUNC*****************************************************************/
334 
DoBatchMode(int argc,char * argv[])335 TV_BOOL DoBatchMode( int argc, char *argv[] )
336 {
337     TV_BOOL batch_mode = FALSE;
338     int     i;
339     char   *mode         = NULL,
340            *stream_input = NULL,
341            *p,
342            *tok;
343     TV_BATCH_PARM parm;
344 
345     memset( &parm, '\0', sizeof(parm) );
346 
347     /*  Was batch mode requested?  */
348     batch_mode = (( argc >= 2 ) && STREQ( argv[1], "-batch" ));
349     if ( !batch_mode )
350         return FALSE;
351 
352     if ( argc == 2 ) {
353         fprintf( stderr, "No batch mode specified after -batch\n" );
354         exit(1);
355     }
356     mode = argv[2];
357 
358     /*  Yes.  Parse args  */
359     for ( i = 3; i < argc; i++ ) {
360         if ( STREQ( argv[i], "-frameformat" ) ) {
361             if ( i+1 >= argc ) {
362                 fprintf( stderr, "No format after -frameformat\n" );
363                 exit(1);
364             }
365             parm.frame_fmt = argv[++i];
366         }
367         else if ( STREQ( argv[i], "-streaminput" ) ) {
368             if ( i+1 >= argc ) {
369                 fprintf( stderr, "No input filelist after -streaminput\n" );
370                 exit(1);
371             }
372             stream_input = argv[++i];
373         }
374         else if ( STREQ( argv[i], "-streamfps" ) ) {
375             if ( i+1 >= argc ) {
376                 fprintf( stderr, "No FPS after -streamfps\n" );
377                 exit(1);
378             }
379             parm.stream_fps = atoi( argv[++i] );
380         }
381         else if ( STREQ( argv[i], "-videotarget" ) ) {
382             if ( i+1 >= argc ) {
383                 fprintf( stderr, "No file/stream after -videotarget\n" );
384                 exit(1);
385             }
386             parm.video_target = argv[++i];
387         }
388         else if ( STREQ( argv[i], "-audiotarget" ) ) {
389             if ( i+1 >= argc ) {
390                 fprintf( stderr, "No file/stream after -audiotarget\n" );
391                 exit(1);
392             }
393             parm.audio_target = argv[++i];
394         }
395     }
396 
397     /*  Set up cleanup signal handler  */
398     signal( SIGHUP , TVBatchIntrHdlr );
399     signal( SIGINT , TVBatchIntrHdlr );
400     signal( SIGQUIT, TVBatchIntrHdlr );
401     signal( SIGPIPE, TVBatchIntrHdlr );
402     signal( SIGTERM, TVBatchIntrHdlr );
403 
404     i = 0;
405     if ( stream_input )
406         for ( i=0, p = stream_input; (tok = strsep( &p, "," )) != NULL; ) {
407             if ( *tok == '\0' )
408                 continue;
409             strncat( parm.stream_input[i++], tok,
410                      sizeof(parm.stream_input[0]) );
411         }
412     parm.stream_num_input = i;
413 
414     if ( STREQ( mode, "streamavcap" ) )
415         DoStreamVideo( &parm );
416     else {
417         fprintf( stderr, "Unknown batch mode: %s\n", mode );
418         exit(1);
419     }
420 
421     return TRUE;
422 }
423