1 /*
2  *  playdv.c
3  *
4  *     Copyright (C) Charles 'Buck' Krasic - April 2000
5  *     Copyright (C) Erik Walthinsen - April 2000
6  *
7  *  This file is part of libdv, a free DV (IEC 61834/SMPTE 314M)
8  *  codec.
9  *
10  *  libdv is free software; you can redistribute it and/or modify it
11  *  under the terms of the GNU Lesser Public License as published by
12  *  the Free Software Foundation; either version 2.1, or (at your
13  *  option) any later version.
14  *
15  *  libdv is distributed in the hope that it will be useful, but
16  *  WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  *  Lesser Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser Public License
21  *  along with libdv; see the file COPYING.  If not, write to
22  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23  *
24  *  The libdv homepage is http://libdv.sourceforge.net/.
25  */
26 
27 #if HAVE_CONFIG_H
28 # include <config.h>
29 #endif
30 
31 #define _FILE_OFFSET_BITS 64
32 
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <sys/time.h>
36 #include <unistd.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <sys/mman.h>
40 #include <fcntl.h>
41 #include <string.h>
42 
43 #include "libdv/dv.h"
44 #include "display.h"
45 #include "oss.h"
46 
47 #ifndef        timersub
48 # define timersub(a, b, result)                          \
49   do {                                                   \
50     (result)->tv_sec = (a)->tv_sec - (b)->tv_sec;        \
51     (result)->tv_usec = (a)->tv_usec - (b)->tv_usec;     \
52     if ((result)->tv_usec < 0) {                         \
53       --(result)->tv_sec;                                \
54       (result)->tv_usec += 1000000;                      \
55     }                                                    \
56   } while (0)
57 #endif
58 
59 #define DV_PLAYER_OPT_VERSION         0
60 #define DV_PLAYER_OPT_DISABLE_AUDIO   1
61 #define DV_PLAYER_OPT_DISABLE_VIDEO   2
62 #define DV_PLAYER_OPT_NUM_FRAMES      3
63 #define DV_PLAYER_OPT_OSS_INCLUDE     4
64 #define DV_PLAYER_OPT_DISPLAY_INCLUDE 5
65 #define DV_PLAYER_OPT_DECODER_INCLUDE 6
66 #define DV_PLAYER_OPT_AUTOHELP        7
67 #define DV_PLAYER_OPT_DUMP_FRAMES     8
68 #define DV_PLAYER_OPT_NO_MMAP         9
69 #define DV_PLAYER_OPT_LOOP_COUNT      10
70 #define DV_PLAYER_NUM_OPTS            11
71 
72 /* Book-keeping for mmap */
73 typedef struct dv_mmap_region_s {
74   void   *map_start;  /* Start of mapped region (page aligned) */
75   size_t  map_length; /* Size of mapped region */
76   guint8 *data_start; /* Data we asked for */
77 } dv_mmap_region_t;
78 
79 typedef struct {
80   dv_decoder_t    *decoder;
81   dv_display_t    *display;
82   dv_oss_t        *oss;
83   dv_mmap_region_t mmap_region;
84   struct stat      statbuf;
85   struct timeval   tv[3];
86   gint             arg_disable_audio;
87   gint             arg_disable_video;
88   gint             no_mmap;
89   gint             arg_num_frames;
90   char*            arg_dump_frames;
91   gint             arg_loop_count;
92 #if HAVE_LIBPOPT
93   struct poptOption option_table[DV_PLAYER_NUM_OPTS+1];
94 #endif /* HAVE_LIBPOPT */
95 } dv_player_t;
96 
97 dv_player_t *
dv_player_new(void)98 dv_player_new(void)
99 {
100   dv_player_t *result;
101 
102   if(!(result = (dv_player_t *)calloc(1,sizeof(dv_player_t)))) goto no_mem;
103   if(!(result->display = dv_display_new())) goto no_display;
104   if(!(result->oss = dv_oss_new())) goto no_oss;
105   if(!(result->decoder = dv_decoder_new(TRUE, FALSE, FALSE))) goto no_decoder;
106   result->arg_loop_count = 1;
107 
108 #if HAVE_LIBPOPT
109   result->option_table[DV_PLAYER_OPT_VERSION] = (struct poptOption) {
110     longName: "version",
111     shortName: 'v',
112     val: 'v',
113     descrip: "show playdv version number",
114   }; /* version */
115 
116   result->option_table[DV_PLAYER_OPT_DISABLE_AUDIO] = (struct poptOption) {
117     longName: "disable-audio",
118     arg:      &result->arg_disable_audio,
119     descrip:  "skip audio decoding",
120   }; /* disable audio */
121 
122   result->option_table[DV_PLAYER_OPT_DISABLE_VIDEO] = (struct poptOption) {
123     longName: "disable-video",
124     descrip:  "skip video decoding",
125     arg:      &result->arg_disable_video,
126   }; /* disable video */
127 
128   result->option_table[DV_PLAYER_OPT_NUM_FRAMES] = (struct poptOption) {
129     longName:   "num-frames",
130     shortName:  'n',
131     argInfo:    POPT_ARG_INT,
132     arg:        &result->arg_num_frames,
133     argDescrip: "count",
134     descrip:    "stop after <count> frames",
135   }; /* number of frames */
136 
137   result->option_table[DV_PLAYER_OPT_DUMP_FRAMES] = (struct poptOption) {
138     longName:   "dump-frames",
139     argInfo:    POPT_ARG_STRING,
140     arg:        &result->arg_dump_frames,
141     argDescrip: "pattern",
142     descrip:    "dump all frames to file pattern like capture%05d.ppm "
143     "(or - for stdout)"
144   }; /* dump all frames */
145 
146   result->option_table[DV_PLAYER_OPT_NO_MMAP] = (struct poptOption) {
147     longName:   "no-mmap",
148     arg:        &result->no_mmap,
149     descrip:    "don't use mmap for reading. (usefull for pipes)"
150   }; /* no mmap */
151 
152   result->option_table[DV_PLAYER_OPT_LOOP_COUNT] = (struct poptOption) {
153     longName:   "loop-count",
154     shortName:  'l',
155     argInfo:    POPT_ARG_INT,
156     arg:        &result->arg_loop_count,
157     argDescrip: "count",
158     descrip:    "loop playback <count> times, 0 for infinite",
159   }; /* loop count */
160 
161   result->option_table[DV_PLAYER_OPT_OSS_INCLUDE] = (struct poptOption) {
162     argInfo: POPT_ARG_INCLUDE_TABLE,
163     arg:     result->oss->option_table,
164     descrip: "Audio output options",
165   }; /* oss */
166 
167   result->option_table[DV_PLAYER_OPT_DISPLAY_INCLUDE] = (struct poptOption) {
168     argInfo: POPT_ARG_INCLUDE_TABLE,
169     arg:     result->display->option_table,
170     descrip: "Video output options",
171   }; /* display */
172 
173   result->option_table[DV_PLAYER_OPT_DECODER_INCLUDE] = (struct poptOption) {
174     argInfo: POPT_ARG_INCLUDE_TABLE,
175     arg:     result->decoder->option_table,
176     descrip: "Decoder options",
177   }; /* decoder */
178 
179   result->option_table[DV_PLAYER_OPT_AUTOHELP] = (struct poptOption) {
180     argInfo: POPT_ARG_INCLUDE_TABLE,
181     arg:     poptHelpOptions,
182     descrip: "Help options",
183   }; /* autohelp */
184 
185 #endif /* HAVE_LIBPOPT */
186 
187   return(result);
188 
189  no_decoder:
190   free(result->oss);
191  no_oss:
192   free(result->display);
193  no_display:
194   free(result);
195   result = NULL;
196  no_mem:
197   return(result);
198 } /* dv_player_new */
199 
200 
201 /* I decided to try to use mmap for reading the input.  I got a slight
202  * (about %5-10) performance improvement */
mmap_unaligned(int fd,int no_mmap,off_t offset,size_t length,dv_mmap_region_t * mmap_region)203 void mmap_unaligned(int fd, int no_mmap, off_t offset, size_t length,
204 		    dv_mmap_region_t *mmap_region)
205 {
206 	size_t real_length;
207 	off_t  real_offset;
208 	size_t page_size;
209 	size_t start_padding;
210 	void *real_start;
211 
212 	static off_t last_offset = 0;
213 	static size_t last_length = 0;
214 	static size_t overrun_size = 0;
215 	static size_t overrun_offset = 0;
216 
217 	if (no_mmap) {
218 		size_t to_read = length;
219 
220 		if (!mmap_region->map_start) {
221 			mmap_region->map_start = malloc(144000);
222 		}
223 
224 		if (last_offset == offset) {
225 			if (last_length < length) {
226 				to_read -= last_length;
227 			} else if (last_length > length) {
228 				overrun_size = last_length - length;
229 				overrun_offset = length;
230 				last_length = length;
231 				return;
232 			} else {
233 				return;
234 			}
235 		}
236 		last_offset = offset;
237 		last_length = length;
238 		if (overrun_size) {
239 			memmove(mmap_region->map_start,
240 				mmap_region->map_start + overrun_offset,
241 				overrun_size);
242 			if (to_read > overrun_size) {
243 				to_read -= overrun_size;
244 				overrun_size = 0;
245 			} else {
246 				overrun_size -= to_read;
247 				overrun_offset += to_read;
248 				to_read = 0;
249 			}
250 		}
251 		while (to_read > 0) {
252 			int rval = read(fd, mmap_region->map_start
253 					+ length - to_read, to_read);
254 			if (rval == 0) {
255 				if (to_read != length) {
256 					fprintf(stderr, "Short read!\n");
257 					exit(-1);
258 				}
259 				exit(0);
260 			}
261 			if (rval < 0) {
262 				perror("read");
263 				exit(-1);
264 			}
265 			to_read -= rval;
266 		}
267 		mmap_region->map_length = length;
268 		mmap_region->data_start = mmap_region->map_start;
269 	} else {
270 		page_size = getpagesize();
271 		start_padding = offset % page_size;
272 		real_offset = (offset / page_size) * page_size;
273 		real_length = real_offset + start_padding + length;
274 		real_start = mmap(0, real_length, PROT_READ, MAP_SHARED,
275 				  fd, real_offset);
276 
277 		mmap_region->map_start = real_start;
278 		mmap_region->map_length = real_length;
279 		mmap_region->data_start = real_start + start_padding;
280 	}
281 } /* mmap_unaligned */
282 
munmap_unaligned(dv_mmap_region_t * mmap_region,int no_mmap)283 int munmap_unaligned(dv_mmap_region_t *mmap_region, int no_mmap)
284 {
285 	if (no_mmap) {
286 		return 0;
287 	} else {
288 		return(munmap(mmap_region->map_start,mmap_region->map_length));
289 	}
290 } /* munmap_unaligned */
291 
292 int
main(int argc,char * argv[])293 main(int argc,char *argv[])
294 {
295   dv_player_t *dv_player = NULL;
296   const char *filename;     /* name of input file */
297   int fd;
298   off_t offset = 0, eof = 0;
299   guint frame_count = 0;
300   gint i;
301   gdouble seconds = 0.0;
302   gint16 *audio_buffers[4];
303 #if HAVE_LIBPOPT
304   int rc;             /* return code from popt */
305   poptContext optCon; /* context for parsing command-line options */
306 #endif /* HAVE_LIBPOPT */
307 
308   if(!(dv_player = dv_player_new())) goto no_mem;
309 
310 #if HAVE_LIBPOPT
311   /* Parse options using popt */
312   optCon = poptGetContext(NULL, argc, (const char **)argv, dv_player->option_table, 0);
313   poptSetOtherOptionHelp(optCon, "[raw dv file or -- for stdin]");
314 
315   while ((rc = poptGetNextOpt(optCon)) > 0) {
316     switch (rc) {
317     case 'v':
318       goto display_version;
319       break;
320     default:
321       break;
322     } /* switch */
323   } /* while */
324 
325   filename = poptGetArg(optCon);
326   if (rc < -1) goto bad_arg;
327   if((filename == NULL) || !(poptPeekArg(optCon) == NULL))
328 	  filename = "-";
329   poptFreeContext(optCon);
330 #else
331   /* No popt, no usage and no options!  HINT: get popt if you don't
332    * have it yet, it's at: ftp://ftp.redhat.com/pub/redhat/code/popt
333    */
334   filename = argv[1];
335 #endif /* HAVE_LIBOPT */
336 
337   if (strcmp(filename, "-") == 0) {
338 	  dv_player->no_mmap = 1;
339 	  fd = STDIN_FILENO;
340   } else {
341 	  /* Open the input file, do fstat to get it's total size */
342 	  if(-1 == (fd = open(filename,O_RDONLY))) goto openfail;
343   }
344   if (!dv_player->no_mmap) {
345 	  if(fstat(fd, &dv_player->statbuf)) goto fstatfail;
346 	  eof = dv_player->statbuf.st_size;
347   }
348 
349   dv_player->decoder->quality = dv_player->decoder->video->quality;
350 
351   /* Read in header of first frame to see how big frames are */
352   mmap_unaligned(fd,dv_player->no_mmap,0,120000,&dv_player->mmap_region);
353   if(MAP_FAILED == dv_player->mmap_region.map_start) goto map_failed;
354 
355   if(dv_parse_header(dv_player->decoder, dv_player->mmap_region.data_start)< 0)
356     goto header_parse_error;
357 
358   if (dv_format_wide (dv_player->decoder))
359     fprintf (stderr, "format 16:9\n");
360   if (dv_format_normal (dv_player->decoder))
361     fprintf (stderr, "format 4:3\n");
362 
363   fprintf(stderr, "Audio is %.1f kHz, %d bits quantization, "
364 	  "%d channels, emphasis %s\n",
365 	 (float)dv_player->decoder->audio->frequency / 1000.0,
366 	 dv_player->decoder->audio->quantization,
367 	 dv_player->decoder->audio->num_channels,
368 	 (dv_player->decoder->audio->emphasis ? "on" : "off"));
369 
370   munmap_unaligned(&dv_player->mmap_region,dv_player->no_mmap);
371 
372   eof -= dv_player->decoder->frame_size; /* makes loop condition simpler */
373 
374   if(!dv_player->arg_disable_video) {
375     if(!dv_display_init (dv_player->display, &argc, &argv,
376 			 dv_player->decoder->width, dv_player->decoder->height,
377 			 dv_player->decoder->sampling, "playdv", "playdv")) goto no_display;
378   } /* if */
379 
380   dv_player->arg_disable_audio =
381     dv_player->arg_disable_audio || (!dv_oss_init(dv_player->decoder, dv_player->oss));
382 
383   for(i=0; i < 4; i++) {
384     if(!(audio_buffers[i] = malloc(DV_AUDIO_MAX_SAMPLES*sizeof(gint16)))) goto no_mem;
385   } /* if */
386 
387   gettimeofday(dv_player->tv+0,NULL);
388 
389 restart:
390 
391   dv_player->decoder->prev_frame_decoded = 0;
392   for(offset=0;
393       offset <= eof || dv_player->no_mmap;
394       offset += dv_player->decoder->frame_size) {
395 
396      /*
397      * Map the frame's data into memory
398      */
399     mmap_unaligned (fd,dv_player->no_mmap,
400 		    offset, dv_player->decoder->frame_size,
401 		    &dv_player->mmap_region);
402     if (MAP_FAILED == dv_player->mmap_region.map_start) goto map_failed;
403     if (dv_parse_header (dv_player->decoder,
404         		 dv_player->mmap_region.data_start) > 0) {
405       /*
406        * video norm has changed so remap region for current frame
407        */
408       munmap_unaligned (&dv_player->mmap_region,dv_player->no_mmap);
409       mmap_unaligned (fd, dv_player->no_mmap, offset,
410 		      dv_player->decoder->frame_size, &dv_player->mmap_region);
411       if (MAP_FAILED == dv_player->mmap_region.map_start) goto map_failed;
412       dv_display_set_norm (dv_player->display, dv_player->decoder->system);
413     } /* if */
414 
415     /* -----------------------------------------------------------------------
416      * now frame is complete, so we may parse all the rest of info packs
417      */
418     dv_parse_packs (dv_player -> decoder, dv_player -> mmap_region. data_start);
419 
420     /* -----------------------------------------------------------------------
421      * keep track of any possible format changes of dv source material
422      */
423     dv_display_check_format (dv_player->display,
424 			     dv_format_wide (dv_player->decoder));
425 
426     /* Parse and unshuffle audio */
427     if(!dv_player->arg_disable_audio) {
428       if(dv_decode_full_audio(dv_player->decoder, dv_player->mmap_region.data_start, audio_buffers)) {
429 	dv_oss_play(dv_player->decoder, dv_player->oss, audio_buffers);
430       } /* if */
431     } /* if */
432 
433     if(!dv_player->arg_disable_video) {
434 #if 0
435       if (dv_format_wide (dv_player->decoder))
436         fprintf (stderr, "format 16:9\n");
437       if (dv_format_normal (dv_player->decoder))
438         fprintf (stderr, "format 4:3\n");
439 #endif
440 
441       if (!dv_player->decoder->prev_frame_decoded ||
442           dv_frame_changed (dv_player->decoder)) {
443 
444         dv_report_video_error (dv_player -> decoder,
445                                dv_player -> mmap_region. data_start);
446 
447 	/* Parse and decode video */
448 	dv_decode_full_frame(dv_player->decoder, dv_player->mmap_region.data_start,
449 			     dv_player->display->color_space,
450 			     dv_player->display->pixels,
451 			     dv_player->display->pitches);
452 	dv_player->decoder->prev_frame_decoded = 1;
453       } else {
454 	fprintf (stderr, "same_frame\n");
455       }
456 
457       /* ----------------------------------------------------------------------
458        * save all frames. even it was not nessessary to decode
459        */
460       if(dv_player->arg_dump_frames) {
461           FILE* fp;
462           char fname[4096];
463 
464         if (strcmp(dv_player->arg_dump_frames, "-") == 0) {
465           fp = stdout;
466         } else {
467           snprintf(fname, 4096, dv_player->arg_dump_frames,
468                    frame_count);
469           fp = fopen(fname, "w");
470         }
471         fprintf(fp, "P6\n# CREATOR: playdv\n%d %d\n255\n",
472                 dv_player->display->width, dv_player->display->height);
473         fwrite(dv_player->display->pixels[0],
474                3, dv_player->display->width
475                 * dv_player->display->height, fp);
476         if (fp != stdout) {
477           fclose(fp);
478         }
479       }
480       /* Display */
481       dv_display_show(dv_player->display);
482     } /* if  */
483 
484     frame_count++;
485     if((dv_player->arg_num_frames > 0) && (frame_count >= dv_player->arg_num_frames)) {
486       goto end_of_file;
487     } /* if  */
488 #if 0
489 {int dummy;read(0,&dummy,1);}
490 #endif
491 
492     /* Release the frame's data */
493     munmap_unaligned(&dv_player->mmap_region, dv_player->no_mmap);
494 
495   } /* while */
496 
497  end_of_file:
498 
499   /* Handle looping */
500   if (--dv_player->arg_loop_count != 0) {
501     lseek( fd, 0, SEEK_SET);
502     goto restart;
503   }
504 
505   gettimeofday(dv_player->tv+1,NULL);
506   timersub(dv_player->tv+1,dv_player->tv+0,dv_player->tv+2);
507   seconds = (double)dv_player->tv[2].tv_usec / 1000000.0;
508   seconds += dv_player->tv[2].tv_sec;
509   fprintf(stderr,"Processed %d frames in %05.2f seconds (%05.2f fps)\n",
510 	  frame_count, seconds, (double)frame_count/seconds);
511   dv_decoder_free(dv_player->decoder);
512   free(dv_player);
513   exit(0);
514 
515   /* Error handling section */
516 #if HAVE_LIBPOPT
517  display_version:
518   fprintf(stderr,"playdv: version %s, http://libdv.sourceforge.net/\n",
519 	  VERSION);
520   exit(0);
521 
522  bad_arg:
523   /* an error occurred during option processing */
524   fprintf(stderr, "%s: %s\n",
525 	  poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
526 	  poptStrerror(rc));
527   exit(-1);
528 #endif
529  no_display:
530   exit(-1);
531  openfail:
532   perror("open:");
533   exit(-1);
534  fstatfail:
535   perror("fstat:");
536   exit(-1);
537  map_failed:
538   perror("mmap:");
539   exit(-1);
540  header_parse_error:
541   fprintf(stderr,"Parser error reading first header\n");
542   exit(-1);
543  no_mem:
544   fprintf(stderr,"Out of memory\n");
545   exit(-1);
546 } /* main */
547