root/liboggz/trunk/src/tools/oggz-validate.c

Revision 3811, 15.4 kB (checked in by conrad, 1 month ago)

print serialno as unsigned throughout

Line 
1 /*
2    Copyright (C) 2003 Commonwealth Scientific and Industrial Research
3    Organisation (CSIRO) Australia
4
5    Redistribution and use in source and binary forms, with or without
6    modification, are permitted provided that the following conditions
7    are met:
8
9    - Redistributions of source code must retain the above copyright
10    notice, this list of conditions and the following disclaimer.
11
12    - Redistributions in binary form must reproduce the above copyright
13    notice, this list of conditions and the following disclaimer in the
14    documentation and/or other materials provided with the distribution.
15
16    - Neither the name of CSIRO Australia nor the names of its
17    contributors may be used to endorse or promote products derived from
18    this software without specific prior written permission.
19
20    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23    PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE ORGANISATION OR
24    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include "config.h"
34
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <getopt.h>
39 #include <errno.h>
40
41 #ifdef HAVE_INTTYPES_H
42 #  include <inttypes.h>
43 #else
44 #  define PRId64 "I64d"
45 #endif
46
47 #include <oggz/oggz.h>
48
49 #include "oggz_tools.h"
50
51 #define MAX_ERRORS 10
52
53 #define SUBSECONDS 1000.0
54
55 /* #define DEBUG */
56
57 typedef ogg_int64_t timestamp_t;
58
59 typedef struct _OVData {
60   OGGZ * writer;
61   OggzTable * missing_eos;
62   OggzTable * packetno;
63
64   int theora_count;
65   int audio_count;
66
67   int chain_ended;
68 } OVData;
69
70 typedef struct {
71   int error;
72   char * description;
73 } error_text;
74
75 static error_text errors[] = {
76   {-20, "Packet belongs to unknown serialno"},
77   {-24, "Granulepos decreasing within track"},
78   {-5, "Multiple bos pages"},
79   {-6, "Multiple eos pages"},
80   {0, NULL}
81 };
82
83 static int max_errors = MAX_ERRORS;
84 static int multifile = 0;
85 static char * current_filename = NULL;
86 static timestamp_t current_timestamp = 0;
87 static int exit_status = 0;
88 static int nr_errors = 0;
89 static int prefix = 0, suffix = 0;
90
91 static void
92 list_errors (void)
93 {
94   int i = 0;
95
96   printf ("  File contains no Ogg packets\n");
97   printf ("  Packets out of order\n");
98   for (i = 0; errors[i].error; i++) {
99     printf ("  %s\n", errors[i].description);
100   }
101   printf ("  eos marked but no bos\n");
102   printf ("  Missing eos pages\n");
103   printf ("  eos marked on page with no completed packets\n");
104   printf ("  Granulepos on page with no completed packets\n");
105   printf ("  Theora video bos page after audio bos page\n");
106   printf ("  Terminal header page has non-zero granulepos\n");
107   printf ("  Terminal header page contains non-header packet\n");
108   printf ("  Terminal header page contains non-header segment\n");
109 }
110
111 static void
112 usage (char * progname)
113 {
114
115   printf ("Usage: %s [options] filename ...\n", progname);
116   printf ("Validate the Ogg framing of one or more files\n");
117   printf ("\n%s detects the following errors in Ogg framing:\n", progname);
118
119   list_errors ();
120
121   printf ("\nError reporting options\n");
122   printf ("  -M num, --max-errors num\n");
123   printf ("                         Exit after the specified number of errors.\n");
124   printf ("                         A value of 0 specifies no maximum. Default: %d\n", MAX_ERRORS);
125   printf ("  -p, --prefix           Treat input as the prefix of a stream; suppress\n");
126   printf ("                         warnings about missing end-of-stream markers\n");
127   printf ("  -s, --suffix           Treat input as the suffix of a stream; suppress\n");
128   printf ("                         warnings about missing beginning-of-stream markers\n");
129   printf ("                         on the first chain\n");
130   printf ("  -P, --partial          Treat input as a the middle portion of a stream;\n");
131   printf ("                         equivalent to both --prefix and --suffix\n");
132
133   printf ("\nMiscellaneous options\n");
134   printf ("  -h, --help             Display this help and exit\n");
135   printf ("  -E, --help-errors      List known types of error and exit\n");
136   printf ("  -v, --version          Output version information and exit\n");
137   printf ("\n");
138   printf ("Exit status is 0 if all input files are valid, 1 otherwise.\n\n");
139   printf ("Please report bugs to <ogg-dev@xiph.org>\n");
140 }
141
142 static int
143 log_error (void)
144 {
145   if (multifile && nr_errors == 0) {
146     fprintf (stderr, "%s: Error:\n", current_filename);
147   }
148
149   exit_status = 1;
150
151   nr_errors++;
152   if (max_errors && nr_errors > max_errors)
153     return OGGZ_STOP_ERR;
154
155   return OGGZ_STOP_OK;
156 }
157
158 static ogg_int64_t
159 gp_to_granule (OGGZ * oggz, long serialno, ogg_int64_t granulepos)
160 {
161   int granuleshift;
162   ogg_int64_t iframe, pframe, granule;
163
164   granuleshift = oggz_get_granuleshift (oggz, serialno);
165
166   iframe = granulepos >> granuleshift;
167   pframe = granulepos - (iframe << granuleshift);
168   granule = iframe+pframe;
169
170   if (oggz_stream_get_content (oggz, serialno) == OGGZ_CONTENT_DIRAC)
171     granule >>= 9;
172
173   return granule;
174 }
175
176 static timestamp_t
177 gp_to_time (OGGZ * oggz, long serialno, ogg_int64_t granulepos)
178 {
179   ogg_int64_t gr_n, gr_d;
180   ogg_int64_t granule;
181
182   if (granulepos == -1) return -1.0;
183   if (oggz_get_granulerate (oggz, serialno, &gr_n, &gr_d) != 0) return -1.0;
184
185   granule = gp_to_granule (oggz, serialno, granulepos);
186
187   return (timestamp_t)((double)(SUBSECONDS * granule * gr_d) / (double)gr_n);
188 }
189
190 static void
191 ovdata_init (OVData * ovdata)
192 {
193   int flags;
194
195   current_timestamp = 0;
196
197   flags = OGGZ_WRITE|OGGZ_AUTO;
198   if (prefix) flags |= OGGZ_PREFIX;
199   if (suffix) flags |= OGGZ_SUFFIX;
200
201   if ((ovdata->writer = oggz_new (flags)) == NULL) {
202     fprintf (stderr, "oggz-validate: unable to create new writer\n");
203     exit (1);
204   }
205
206   ovdata->missing_eos = oggz_table_new ();
207   ovdata->packetno = oggz_table_new ();
208   ovdata->theora_count = 0;
209   ovdata->audio_count = 0;
210   ovdata->chain_ended = 0;
211 }
212
213 static void
214 ovdata_clear (OVData * ovdata)
215 {
216   long serialno;
217   int i, nr_missing_eos = 0;
218
219   oggz_close (ovdata->writer);
220
221   if (!prefix && (max_errors == 0 || nr_errors <= max_errors)) {
222     nr_missing_eos = oggz_table_size (ovdata->missing_eos);
223     for (i = 0; i < nr_missing_eos; i++) {
224       log_error ();
225       oggz_table_nth (ovdata->missing_eos, i, &serialno);
226       fprintf (stderr, "serialno %010lu: missing *** eos\n", serialno);
227     }
228   }
229
230   oggz_table_delete (ovdata->missing_eos);
231   oggz_table_delete (ovdata->packetno);
232 }
233
234 static int
235 read_page (OGGZ * oggz, const ogg_page * og, long serialno, void * user_data)
236 {
237   OVData * ovdata = (OVData *)user_data;
238   ogg_int64_t gpos = ogg_page_granulepos((ogg_page *)og);
239   OggzStreamContent content_type;
240   int packets, packetno, headers, ret = 0;
241
242   if (ovdata->chain_ended) {
243     ovdata_clear (ovdata);
244     ovdata_init (ovdata);
245     suffix = 0;
246   }
247
248   if (ogg_page_bos ((ogg_page *)og)) {
249     /* Register this serialno as needing eos */
250     oggz_table_insert (ovdata->missing_eos, serialno, (void *)0x1);
251
252     /* Handle ordering of Theora vs. audio packets */
253     content_type = oggz_stream_get_content (oggz, serialno);
254
255     switch (content_type) {
256       case OGGZ_CONTENT_THEORA:
257         ovdata->theora_count++;
258         if (ovdata->audio_count > 0) {
259           log_error ();
260           fprintf (stderr, "serialno %010lu: Theora video bos page after audio bos page\n", serialno);
261         }
262         break;
263       case OGGZ_CONTENT_VORBIS:
264       case OGGZ_CONTENT_SPEEX:
265       case OGGZ_CONTENT_PCM:
266       case OGGZ_CONTENT_FLAC0:
267       case OGGZ_CONTENT_FLAC:
268       case OGGZ_CONTENT_CELT:
269         ovdata->audio_count++;
270         break;
271       default:
272         break;
273     }
274   }
275
276   packets = ogg_page_packets ((ogg_page *)og);
277
278   /* Check header constraints */
279   if (!suffix) {
280     if (oggz_table_lookup (ovdata->missing_eos, serialno) == NULL) {
281       ret = log_error ();
282       fprintf (stderr, "serialno %010lu: missing *** bos\n", serialno);
283     }
284
285     packetno = (int)oggz_table_lookup (ovdata->packetno, serialno);
286     headers = oggz_stream_get_numheaders (oggz, serialno);
287     if (packetno < headers) {
288       /* The previous page was headers, and more are expected */
289       packetno += packets;
290       oggz_table_insert (ovdata->packetno, serialno, (void *)packetno);
291       if (packetno == headers && gpos != 0) {
292         ret = log_error ();
293         fprintf (stderr, "serialno %010lu: Terminal header page has non-zero granulepos\n", serialno);
294       } else if (packetno > headers) {
295         ret = log_error ();
296         fprintf (stderr, "serialno %010lu: Terminal header page contains non-header packet\n", serialno);
297       }
298     } else if (packetno == headers) {
299       /* This is the next page after the page on which the last header finished */
300       if (ogg_page_continued (og)) {
301         ret = log_error ();
302         fprintf (stderr, "serialno %010lu: Terminal header page contains non-header segment\n", serialno);
303       }
304
305       /* Mark packetno as greater than headers to avoid these checks for this serialno */
306       oggz_table_insert (ovdata->packetno, serialno, (void *)(headers+1));
307     }
308
309   }
310
311   /* Check EOS */
312   if (ogg_page_eos((ogg_page *)og)) {
313     int removed = oggz_table_remove (ovdata->missing_eos, serialno);
314     if (!suffix && removed == -1) {
315       ret = log_error ();
316       fprintf (stderr, "serialno %010lu: *** eos marked but no bos\n",
317                serialno);
318     }
319
320     if (packets == 0) {
321       ret = log_error ();
322       fprintf (stderr, "serialno %010lu: *** eos marked on page with no completed packets\n",
323                serialno);
324     }
325
326     if (oggz_table_size (ovdata->missing_eos) == 0) {
327       ovdata->chain_ended = 1;
328     }
329   }
330
331
332   if(gpos != -1 && packets == 0) {
333     ret = log_error ();
334     fprintf (stderr, "serialno %010lu: granulepos %" PRId64 " on page with no completed packets, must be -1\n", serialno, gpos);
335   }
336
337   return ret;
338 }
339
340 static int
341 read_packet (OGGZ * oggz, ogg_packet * op, long serialno, void * user_data)
342 {
343   OVData * ovdata = (OVData *)user_data;
344   timestamp_t timestamp;
345   int flush;
346   int ret = 0, feed_err = 0, i;
347
348   timestamp = gp_to_time (oggz, serialno, op->granulepos);
349   if (timestamp != -1.0 && oggz_stream_get_content (oggz, serialno) != OGGZ_CONTENT_DIRAC) {
350     if (timestamp < current_timestamp) {
351       ret = log_error();
352       ot_fprint_time (stderr, (double)timestamp/SUBSECONDS);
353       fprintf (stderr, ": serialno %010lu: Packet out of order (previous ",
354                serialno);
355       ot_fprint_time (stderr, (double)current_timestamp/SUBSECONDS);
356       fprintf (stderr, ")\n");
357     }
358     current_timestamp = timestamp;
359   }
360
361   if (op->granulepos == -1) {
362     flush = 0;
363   } else {
364     flush = OGGZ_FLUSH_AFTER;
365   }
366
367   if ((feed_err = oggz_write_feed (ovdata->writer, op, serialno, flush, NULL)) != 0) {
368     ret = log_error ();
369     if (timestamp == -1.0) {
370       fprintf (stderr, "%" PRId64 , oggz_tell (oggz));
371     } else {
372       ot_fprint_time (stderr, (double)timestamp/SUBSECONDS);
373     }
374     fprintf (stderr, ": serialno %010lu: ", serialno);
375     for (i = 0; errors[i].error; i++) {
376       if (errors[i].error == feed_err) {
377         fprintf (stderr, "%s\n", errors[i].description);
378         break;
379       }
380     }
381     if (errors[i].error == 0) {
382       fprintf (stderr,
383                "Packet violates Ogg framing constraints: %d\n",
384                feed_err);
385     }
386   }
387
388   return ret;
389 }
390
391 static int
392 validate (char * filename)
393 {
394   OGGZ * reader;
395   OVData ovdata;
396   unsigned char buf[1024];
397   long n, nout = 0, bytes_written = 0;
398   int active = 1;
399
400   current_filename = filename;
401   current_timestamp = 0;
402   nr_errors = 0;
403
404   /*printf ("oggz-validate: %s\n", filename);*/
405
406   if (!strncmp (filename, "-", 2)) {
407     if ((reader = oggz_open_stdio (stdin, OGGZ_READ|OGGZ_AUTO)) == NULL) {
408       fprintf (stderr, "oggz-validate: unable to open stdin\n");
409       return -1;
410     }
411   } else if ((reader = oggz_open (filename, OGGZ_READ|OGGZ_AUTO)) == NULL) {
412     fprintf (stderr, "oggz-validate: unable to open file %s\n", filename);
413     return -1;
414   }
415
416   ovdata_init (&ovdata);
417
418   oggz_set_read_callback (reader, -1, read_packet, &ovdata);
419   oggz_set_read_page (reader, -1, read_page, &ovdata);
420
421   while (active && (n = oggz_read (reader, 1024)) != 0) {
422 #ifdef DEBUG
423       fprintf (stderr, "validate: read %ld bytes\n", n);
424 #endif
425     
426     if (max_errors && nr_errors > max_errors) {
427       fprintf (stderr,
428                "oggz-validate --max-errors %d: maximum error count reached, bailing out ...\n",
429                max_errors);
430       active = 0;
431     } else while ((nout = oggz_write_output (ovdata.writer, buf, n)) > 0) {
432 #ifdef DEBUG
433       fprintf (stderr, "validate: wrote %ld bytes\n", nout);
434 #endif
435       bytes_written += nout;
436     }
437   }
438
439   oggz_close (reader);
440
441   if (bytes_written == 0) {
442     log_error ();
443     fprintf (stderr, "File contains no Ogg packets\n");
444   }
445
446   ovdata_clear (&ovdata);
447
448   return active ? 0 : -1;
449 }
450
451 int
452 main (int argc, char ** argv)
453 {
454   int show_version = 0;
455   int show_help = 0;
456
457   /* Cache the --prefix, --suffix options and reset before validating
458    * each input file */
459   int opt_prefix = 0;
460   int opt_suffix = 0;
461
462   char * progname;
463   char * filename;
464   int i = 1;
465
466   char * optstring = "M:psPhvE";
467
468 #ifdef HAVE_GETOPT_LONG
469   static struct option long_options[] = {
470     {"max-errors", required_argument, 0, 'M'},
471     {"prefix", no_argument, 0, 'p'},
472     {"suffix", no_argument, 0, 's'},
473     {"partial", no_argument, 0, 'P'},
474     {"help", no_argument, 0, 'h'},
475     {"help-errors", no_argument, 0, 'E'},
476     {"version", no_argument, 0, 'v'},
477     {0,0,0,0}
478   };
479 #endif
480
481   ot_init();
482
483   progname = argv[0];
484
485   if (argc < 2) {
486     usage (progname);
487     return (1);
488   }
489
490   if (!strncmp (argv[1], "-?", 2)) {
491 #ifdef HAVE_GETOPT_LONG
492     ot_print_options (long_options, optstring);
493 #else
494     ot_print_short_options (optstring);
495 #endif
496     exit (0);
497   }
498
499   while (1) {
500 #ifdef HAVE_GETOPT_LONG
501     i = getopt_long(argc, argv, optstring, long_options, NULL);
502 #else
503     i = getopt (argc, argv, optstring);
504 #endif
505     if (i == -1) {
506       break;
507     }
508     if (i == ':') {
509       usage (progname);
510       goto exit_err;
511     }
512
513     switch (i) {
514     case 'M': /* max-errors */
515       max_errors = atoi (optarg);
516       break;
517     case 'p': /* prefix */
518       opt_prefix = 1;
519       break;
520     case 's': /* suffix */
521       opt_suffix = 1;
522       break;
523     case 'P': /* partial */
524       opt_prefix = 1;
525       opt_suffix = 1;
526       break;
527     case 'h': /* help */
528       show_help = 1;
529       break;
530     case 'v': /* version */
531       show_version = 1;
532       break;
533     case 'E': /* list errors */
534       show_help = 2;
535       break;
536     default:
537       break;
538     }
539   }
540
541   if (show_version) {
542     printf ("%s version " VERSION "\n", progname);
543   }
544
545   if (show_help == 1) {
546     usage (progname);
547   } else if (show_help == 2) {
548     list_errors ();
549   }
550
551   if (show_version || show_help) {
552     goto exit_out;
553   }
554
555   if (max_errors < 0) {
556     printf ("%s: Error: [-M num, --max-errors num] option must be non-negative\n", progname);
557     goto exit_err;
558   }
559
560   if (optind >= argc) {
561     usage (progname);
562     goto exit_err;
563   }
564
565   if (argc-i > 2) multifile = 1;
566
567   for (i = optind; i < argc; i++) {
568     filename = argv[i];
569     prefix = opt_prefix;
570