oggz-validate.c 13.4 KB
Newer Older
conrad's avatar
conrad committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
   Copyright (C) 2003 Commonwealth Scientific and Industrial Research
   Organisation (CSIRO) Australia

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:

   - Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

   - Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

   - Neither the name of CSIRO Australia nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
   PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE ORGANISATION OR
   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

33
34
#include "config.h"

conrad's avatar
conrad committed
35
36
#include <stdio.h>
#include <stdlib.h>
37
#include <string.h>
conrad's avatar
conrad committed
38
39
40
#include <getopt.h>
#include <errno.h>

41
42
43
44
45
46
#ifdef HAVE_INTTYPES_H
#  include <inttypes.h>
#else
#  define PRId64 "I64d"
#endif

conrad's avatar
conrad committed
47
48
49
50
51
52
53
54
#include <oggz/oggz.h>

#include "oggz_tools.h"

#define MAX_ERRORS 10

#define SUBSECONDS 1000.0

55
56
/* #define DEBUG */

conrad's avatar
conrad committed
57
58
typedef ogg_int64_t timestamp_t;

59
60
61
typedef struct _OVData {
  OGGZ * writer;
  OggzTable * missing_eos;
62
63
  int theora_count;
  int audio_count;
64
65

  int chain_ended;
66
67
} OVData;

conrad's avatar
conrad committed
68
69
70
71
72
73
typedef struct {
  int error;
  char * description;
} error_text;

static error_text errors[] = {
74
75
  {-20, "Packet belongs to unknown serialno"},
  {-24, "Granulepos decreasing within track"},
76
77
  {-5, "Multiple bos pages"},
  {-6, "Multiple eos pages"},
conrad's avatar
conrad committed
78
79
80
  {0, NULL}
};

81
static int max_errors = MAX_ERRORS;
conrad's avatar
conrad committed
82
83
84
85
86
static int multifile = 0;
static char * current_filename = NULL;
static timestamp_t current_timestamp = 0;
static int exit_status = 0;
static int nr_errors = 0;
87
static int prefix = 0, suffix = 0;
conrad's avatar
conrad committed
88
89

static void
90
list_errors (void)
conrad's avatar
conrad committed
91
92
93
{
  int i = 0;

94
95
  printf ("  File contains no Ogg packets\n");
  printf ("  Packets out of order\n");
conrad's avatar
conrad committed
96
97
98
  for (i = 0; errors[i].error; i++) {
    printf ("  %s\n", errors[i].description);
  }
99
  printf ("  eos marked but no bos\n");
100
101
  printf ("  Missing eos pages\n");
  printf ("  eos marked on page with no completed packets\n");
102
103
  printf ("  Granulepos on page with no completed packets\n");
  printf ("  Theora video bos page after audio bos page\n");
104
105
106
107
108
109
110
111
112
113
114
}
static void
usage (char * progname)
{

  printf ("Usage: %s [options] filename ...\n", progname);
  printf ("Validate the Ogg framing of one or more files\n");
  printf ("\n%s detects the following errors in Ogg framing:\n", progname);

  list_errors ();

115
116
117
118
  printf ("\nError reporting options\n");
  printf ("  -M num, --max-errors num\n");
  printf ("                         Exit after the specified number of errors.\n");
  printf ("                         A value of 0 specifies no maximum. Default: %d\n", MAX_ERRORS);
119
120
121
122
123
124
125
126
  printf ("  -p, --prefix           Treat input as the prefix of a stream; suppress\n");
  printf ("                         warnings about missing end-of-stream markers\n");
  printf ("  -s, --suffix           Treat input as the suffix of a stream; suppress\n");
  printf ("                         warnings about missing beginning-of-stream markers\n");
  printf ("                         on the first chain\n");
  printf ("  -P, --partial          Treat input as a the middle portion of a stream;\n");
  printf ("                         equivalent to both --prefix and --suffix\n");

conrad's avatar
conrad committed
127
128
  printf ("\nMiscellaneous options\n");
  printf ("  -h, --help             Display this help and exit\n");
129
  printf ("  -E, --help-errors      List known types of error and exit\n");
conrad's avatar
conrad committed
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
  printf ("  -v, --version          Output version information and exit\n");
  printf ("\n");
  printf ("Exit status is 0 if all input files are valid, 1 otherwise.\n\n");
  printf ("Please report bugs to <ogg-dev@xiph.org>\n");
}

static int
log_error (void)
{
  if (multifile && nr_errors == 0) {
    fprintf (stderr, "%s: Error:\n", current_filename);
  }

  exit_status = 1;

  nr_errors++;
146
  if (max_errors && nr_errors > max_errors)
conrad's avatar
conrad committed
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
    return OGGZ_STOP_ERR;

  return OGGZ_STOP_OK;
}

static ogg_int64_t
gp_to_granule (OGGZ * oggz, long serialno, ogg_int64_t granulepos)
{
  int granuleshift;
  ogg_int64_t iframe, pframe;

  granuleshift = oggz_get_granuleshift (oggz, serialno);

  iframe = granulepos >> granuleshift;
  pframe = granulepos - (iframe << granuleshift);

  return (iframe + pframe);
}

static timestamp_t
gp_to_time (OGGZ * oggz, long serialno, ogg_int64_t granulepos)
{
  ogg_int64_t gr_n, gr_d;
  ogg_int64_t granule;

  if (granulepos == -1) return -1.0;
  if (oggz_get_granulerate (oggz, serialno, &gr_n, &gr_d) != 0) return -1.0;

  granule = gp_to_granule (oggz, serialno, granulepos);

  return (timestamp_t)((double)(granule * gr_d) / (double)gr_n);
}

180
181
182
static void
ovdata_init (OVData * ovdata)
{
183
184
  int flags;

185
186
  current_timestamp = 0;

187
188
189
190
191
  flags = OGGZ_WRITE|OGGZ_AUTO;
  if (prefix) flags |= OGGZ_PREFIX;
  if (suffix) flags |= OGGZ_SUFFIX;

  if ((ovdata->writer = oggz_new (flags)) == NULL) {
192
193
194
195
196
197
198
    fprintf (stderr, "oggz-validate: unable to create new writer\n");
    exit (1);
  }

  ovdata->missing_eos = oggz_table_new ();
  ovdata->theora_count = 0;
  ovdata->audio_count = 0;
199
  ovdata->chain_ended = 0;
200
201
202
203
204
205
206
207
208
209
}

static void
ovdata_clear (OVData * ovdata)
{
  long serialno;
  int i, nr_missing_eos = 0;

  oggz_close (ovdata->writer);

210
  if (!prefix && (max_errors == 0 || nr_errors <= max_errors)) {
211
212
213
214
215
216
217
218
219
220
221
    nr_missing_eos = oggz_table_size (ovdata->missing_eos);
    for (i = 0; i < nr_missing_eos; i++) {
      log_error ();
      oggz_table_nth (ovdata->missing_eos, i, &serialno);
      fprintf (stderr, "serialno %010ld: missing *** eos\n", serialno);
    }
  }

  oggz_table_delete (ovdata->missing_eos);
}

conrad's avatar
conrad committed
222
static int
223
read_page (OGGZ * oggz, const ogg_page * og, long serialno, void * user_data)
conrad's avatar
conrad committed
224
{
225
  OVData * ovdata = (OVData *)user_data;
226
  ogg_int64_t gpos = ogg_page_granulepos((ogg_page *)og);
227
  OggzStreamContent content_type;
228
  int ret = 0;
conrad's avatar
conrad committed
229

230
231
232
233
234
235
236
237
  if (ovdata->chain_ended) {
    ovdata_clear (ovdata);
    ovdata_init (ovdata);
    suffix = 0;
  }

  if (ogg_page_bos ((ogg_page *)og)) {
    /* Register this serialno as needing eos */
238
    oggz_table_insert (ovdata->missing_eos, serialno, (void *)0x1);
239
240

    /* Handle ordering of Theora vs. audio packets */
241
    content_type = oggz_stream_get_content (oggz, serialno);
242

243
244
    switch (content_type) {
      case OGGZ_CONTENT_THEORA:
245
246
247
248
249
	ovdata->theora_count++;
	if (ovdata->audio_count > 0) {
	  log_error ();
	  fprintf (stderr, "serialno %010ld: Theora video bos page after audio bos page\n", serialno);
	}
250
251
252
253
254
255
256
        break;
      case OGGZ_CONTENT_VORBIS:
      case OGGZ_CONTENT_SPEEX:
      case OGGZ_CONTENT_PCM:
      case OGGZ_CONTENT_FLAC0:
      case OGGZ_CONTENT_FLAC:
      case OGGZ_CONTENT_CELT:
257
	ovdata->audio_count++;
258
259
260
        break;
      default:
        break;
261
    }
262
263
  }

264
  if (!suffix && oggz_table_lookup (ovdata->missing_eos, serialno) == NULL) {
265
266
267
268
    ret = log_error ();
    fprintf (stderr, "serialno %010ld: missing *** bos\n", serialno);
  }

269
270
271
  if (ogg_page_eos((ogg_page *)og)) {
    int removed = oggz_table_remove (ovdata->missing_eos, serialno);
    if (!suffix && removed == -1) {
272
273
      ret = log_error ();
      fprintf (stderr, "serialno %010ld: *** eos marked but no bos\n",
274
275
276
277
278
279
280
281
282
283
284
  	       serialno);
    }

    if (ogg_page_packets((ogg_page *)og) == 0) {
      ret = log_error ();
      fprintf (stderr, "serialno %010ld: *** eos marked on page with no completed packets\n",
  	       serialno);
    }

    if (oggz_table_size (ovdata->missing_eos) == 0) {
      ovdata->chain_ended = 1;
285
286
287
    }
  }

288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304

  if(gpos != -1 && ogg_page_packets((ogg_page *)og) == 0) {
    ret = log_error ();
    fprintf (stderr, "serialno %010ld: granulepos %" PRId64 " on page with no completed packets, must be -1\n", serialno, gpos);
  }

  return ret;
}

static int
read_packet (OGGZ * oggz, ogg_packet * op, long serialno, void * user_data)
{
  OVData * ovdata = (OVData *)user_data;
  timestamp_t timestamp;
  int flush;
  int ret = 0, feed_err = 0, i;

conrad's avatar
conrad committed
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
  timestamp = gp_to_time (oggz, serialno, op->granulepos);
  if (timestamp != -1.0) {
    if (timestamp < current_timestamp) {
      ret = log_error();
      ot_fprint_time (stderr, (double)timestamp/SUBSECONDS);
      fprintf (stderr, ": serialno %010ld: Packet out of order (previous ",
	       serialno);
      ot_fprint_time (stderr, (double)current_timestamp/SUBSECONDS);
      fprintf (stderr, ")\n");
    }
    current_timestamp = timestamp;
  }

  if (op->granulepos == -1) {
    flush = 0;
  } else {
    flush = OGGZ_FLUSH_AFTER;
  }

324
  if ((feed_err = oggz_write_feed (ovdata->writer, op, serialno, flush, NULL)) != 0) {
conrad's avatar
conrad committed
325
326
    ret = log_error ();
    if (timestamp == -1.0) {
327
      fprintf (stderr, "%" PRId64 , oggz_tell (oggz));
conrad's avatar
conrad committed
328
329
330
    } else {
      ot_fprint_time (stderr, (double)timestamp/SUBSECONDS);
    }
331
332
333
334
335
336
337
338
339
340
341
342
    fprintf (stderr, ": serialno %010ld: ", serialno);
    for (i = 0; errors[i].error; i++) {
      if (errors[i].error == feed_err) {
	fprintf (stderr, "%s\n", errors[i].description);
	break;
      }
    }
    if (errors[i].error == 0) {
      fprintf (stderr,
	       "Packet violates Ogg framing constraints: %d\n",
	       feed_err);
    }
conrad's avatar
conrad committed
343
344
345
346
347
348
349
350
  }

  return ret;
}

static int
validate (char * filename)
{
351
352
  OGGZ * reader;
  OVData ovdata;
conrad's avatar
conrad committed
353
  unsigned char buf[1024];
354
355
  long n, nout = 0, bytes_written = 0;
  int active = 1;
conrad's avatar
conrad committed
356
357

  current_filename = filename;
358
  current_timestamp = 0;
conrad's avatar
conrad committed
359
360
361
  nr_errors = 0;

  /*printf ("oggz-validate: %s\n", filename);*/
362

363
364
365
366
367
368
  if (!strncmp (filename, "-", 2)) {
    if ((reader = oggz_open_stdio (stdin, OGGZ_READ|OGGZ_AUTO)) == NULL) {
      fprintf (stderr, "oggz-validate: unable to open stdin\n");
      return -1;
    }
  } else if ((reader = oggz_open (filename, OGGZ_READ|OGGZ_AUTO)) == NULL) {
conrad's avatar
conrad committed
369
    fprintf (stderr, "oggz-validate: unable to open file %s\n", filename);
370
    return -1;
conrad's avatar
conrad committed
371
372
  }

373
  ovdata_init (&ovdata);
conrad's avatar
conrad committed
374

375
  oggz_set_read_callback (reader, -1, read_packet, &ovdata);
376
  oggz_set_read_page (reader, -1, read_page, &ovdata);
conrad's avatar
conrad committed
377
378

  while (active && (n = oggz_read (reader, 1024)) != 0) {
379
380
381
382
#ifdef DEBUG
      fprintf (stderr, "validate: read %ld bytes\n", n);
#endif
    
383
    if (max_errors && nr_errors > max_errors) {
conrad's avatar
conrad committed
384
385
386
      fprintf (stderr,
	       "oggz-validate: maximum error count reached, bailing out ...\n");
      active = 0;
387
    } else while ((nout = oggz_write_output (ovdata.writer, buf, n)) > 0) {
388
389
390
#ifdef DEBUG
      fprintf (stderr, "validate: wrote %ld bytes\n", nout);
#endif
391
392
      bytes_written += nout;
    }
conrad's avatar
conrad committed
393
394
395
396
  }

  oggz_close (reader);

397
398
399
400
401
  if (bytes_written == 0) {
    log_error ();
    fprintf (stderr, "File contains no Ogg packets\n");
  }

402
  ovdata_clear (&ovdata);
403

conrad's avatar
conrad committed
404
405
406
407
408
409
410
411
412
  return active ? 0 : -1;
}

int
main (int argc, char ** argv)
{
  int show_version = 0;
  int show_help = 0;

413
414
415
416
417
  /* Cache the --prefix, --suffix options and reset before validating
   * each input file */
  int opt_prefix = 0;
  int opt_suffix = 0;

conrad's avatar
conrad committed
418
419
420
421
422
423
424
425
426
427
428
429
430
431
  char * progname;
  char * filename;
  int i = 1;

  ot_init();

  progname = argv[0];

  if (argc < 2) {
    usage (progname);
    return (1);
  }

  while (1) {
432
    char * optstring = "M:psPhvE";
conrad's avatar
conrad committed
433
434
435

#ifdef HAVE_GETOPT_LONG
    static struct option long_options[] = {
436
      {"max-errors", required_argument, 0, 'M'},
437
438
439
      {"prefix", no_argument, 0, 'p'},
      {"suffix", no_argument, 0, 's'},
      {"partial", no_argument, 0, 'P'},
conrad's avatar
conrad committed
440
      {"help", no_argument, 0, 'h'},
441
      {"help-errors", no_argument, 0, 'E'},
conrad's avatar
conrad committed
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
      {"version", no_argument, 0, 'v'},
      {0,0,0,0}
    };

    i = getopt_long(argc, argv, optstring, long_options, NULL);
#else
    i = getopt (argc, argv, optstring);
#endif
    if (i == -1) {
      break;
    }
    if (i == ':') {
      usage (progname);
      goto exit_err;
    }

    switch (i) {
459
460
461
    case 'M': /* max-errors */
      max_errors = atoi (optarg);
      break;
462
463
464
465
466
467
468
469
470
471
    case 'p': /* prefix */
      opt_prefix = 1;
      break;
    case 's': /* suffix */
      opt_suffix = 1;
      break;
    case 'P': /* partial */
      opt_prefix = 1;
      opt_suffix = 1;
      break;
conrad's avatar
conrad committed
472
473
474
475
476
477
    case 'h': /* help */
      show_help = 1;
      break;
    case 'v': /* version */
      show_version = 1;
      break;
478
479
480
    case 'E': /* list errors */
      show_help = 2;
      break;
conrad's avatar
conrad committed
481
482
483
484
485
486
487
488
489
    default:
      break;
    }
  }

  if (show_version) {
    printf ("%s version " VERSION "\n", progname);
  }

490
  if (show_help == 1) {
conrad's avatar
conrad committed
491
    usage (progname);
492
493
  } else if (show_help == 2) {
    list_errors ();
conrad's avatar
conrad committed
494
495
496
497
498
499
  }

  if (show_version || show_help) {
    goto exit_out;
  }

500
501
502
503
504
  if (max_errors < 0) {
    printf ("%s: Error: [-M num, --max-errors num] option must be non-negative\n", progname);
    goto exit_err;
  }

conrad's avatar
conrad committed
505
506
507
508
509
510
511
  if (optind >= argc) {
    usage (progname);
    goto exit_err;
  }

  if (argc-i > 2) multifile = 1;

512
  for (i = optind; i < argc; i++) {
conrad's avatar
conrad committed
513
    filename = argv[i];
514
515
    prefix = opt_prefix;
    suffix = opt_suffix;
516
517
    if (validate (filename) == -1)
      exit_status = 1;
conrad's avatar
conrad committed
518
519
520
521
522
523
524
525
  }

 exit_out:
  exit (exit_status);

 exit_err:
  exit (1);
}