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