oggzinfo.c 15.2 KB
Newer Older
andre's avatar
andre 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 33 34
/*
   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.
*/

#include <stdio.h>
#include <stdlib.h>
35
#include <string.h>
conrad's avatar
conrad committed
36 37
#include <limits.h> /* LONG_MAX */
#include <math.h>
andre's avatar
andre committed
38

39 40 41 42
#include <getopt.h>
#include <errno.h>

#include <oggz/oggz.h>
conrad's avatar
conrad committed
43 44
#include "oggz_tools.h"

45 46
#include "skeleton.h"

conrad's avatar
conrad committed
47 48
#ifdef HAVE_INTTYPES_H
#  include <inttypes.h>
49
#else
conrad's avatar
conrad committed
50
#  define PRId64 "I64d"
51 52
#endif

conrad's avatar
conrad committed
53 54
#define READ_BLOCKSIZE 1024000

55 56 57 58
static void
usage (char * progname)
{
  printf ("Usage: %s [options] filename ...\n", progname);
59
  printf ("Display information about one or more Ogg files and their bitstreams\n");
60 61 62 63 64 65 66 67 68 69
  printf ("\nDisplay options\n");
  printf ("  -l, --length           Display content lengths\n");
  printf ("  -b, --bitrate          Display bitrate information\n");
  printf ("  -g, --page-stats       Display Ogg page statistics\n");
  printf ("  -p, --packet-stats     Display Ogg packet statistics\n");
  printf ("  -a, --all              Display all information\n");
  printf ("\nMiscellaneous options\n");
  printf ("  -h, --help             Display this help and exit\n");
  printf ("  -v, --version          Output version information and exit\n");
  printf ("\n");
70 71 72 73 74 75 76 77 78 79 80 81
  printf ("Byte lengths are displayed using the following units:\n");
  printf ("  bytes (8 bits)\n");
  printf ("  kB    kilobytes (1024 bytes)\n");
  printf ("  MB    megabytes (1024*1024 bytes)\n");
  printf ("  GB    gigabytes (1024*1024*1024 bytes)\n");
  printf ("\n");
  printf ("Bitrates are displayed using the following units:\n");
  printf ("  bps   bits per second     (bit/s)\n");
  printf ("  kbps  kilobits per second (1000 bit/s)\n");
  printf ("  Mbps  megabits per second (1000000 bit/s)\n");
  printf ("  Gbps  gigabits per second (1000000000 bit/s)\n");
  printf ("\n");
82 83 84
  printf ("Please report bugs to <ogg-dev@xiph.org>\n");
}

85 86
#define SEP "------------------------------------------------------------"

conrad's avatar
conrad committed
87 88 89
typedef struct _OI_Info OI_Info;
typedef struct _OI_Stats OI_Stats;
typedef struct _OI_TrackInfo OI_TrackInfo;
90

conrad's avatar
conrad committed
91 92
/* Let's get functional */
typedef void (*OI_TrackFunc) (OI_Info * info, OI_TrackInfo * oit, long serialno);
andre's avatar
andre committed
93

conrad's avatar
conrad committed
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
struct _OI_Info {
  OggzTable * tracks;
  ogg_int64_t duration;
  long length_total;
};

struct _OI_Stats {
  /* Pass 1 */
  long count;
  long length_total;
  long length_min;
  long length_max;

  /* Pass 2 */
  long length_avg;
  long length_deviation_total;
  double length_stddev;
};

struct _OI_TrackInfo {
  OI_Stats pages;
  OI_Stats packets;
conrad's avatar
conrad committed
116
  const char * codec_name;
117
  char * codec_info;
118 119 120 121
  int has_fishead;
  int has_fisbone;
  fishead_packet fhInfo;
  fisbone_packet fbInfo;
conrad's avatar
conrad committed
122 123
};

124 125 126 127
static int show_length = 0;
static int show_bitrate = 0;
static int show_page_stats = 0;
static int show_packet_stats = 0;
128
static int show_extra_skeleton_info = 0;
129

conrad's avatar
conrad committed
130 131
static void
oggzinfo_apply (OI_TrackFunc func, OI_Info * info)
andre's avatar
andre committed
132
{
conrad's avatar
conrad committed
133 134 135
  OI_TrackInfo * oit;
  long serialno;
  int n, i;
andre's avatar
andre committed
136

conrad's avatar
conrad committed
137 138 139 140
  n = oggz_table_size (info->tracks);
  for (i = 0; i < n; i++) {
    oit = oggz_table_nth (info->tracks, i, &serialno);
    if (oit) func (info, oit, serialno);
andre's avatar
andre committed
141
  }
conrad's avatar
conrad committed
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
}

static void
oi_stats_clear (OI_Stats * stats)
{
  stats->count = 0;

  stats->length_total = 0;
  stats->length_min = LONG_MAX;
  stats->length_max = 0;

  stats->length_avg = 0;
  stats->length_deviation_total = 0;
  stats->length_stddev = 0;
}

static OI_TrackInfo *
oggzinfo_trackinfo_new (void)
{
  OI_TrackInfo * oit;

  oit = malloc (sizeof (OI_TrackInfo));

  oi_stats_clear (&oit->pages);
  oi_stats_clear (&oit->packets);

168 169 170
  oit->codec_name = NULL;
  oit->codec_info = NULL;

171 172 173
  oit->has_fishead = 0;
  oit->has_fisbone = 0;

conrad's avatar
conrad committed
174 175 176 177 178 179
  return oit;
}

static long
oi_bitrate (long bytes, ogg_int64_t ms)
{
180 181
  if (ms == 0) return 0;
  else return (long) (((ogg_int64_t)bytes * 8 * 1000) / ms);
conrad's avatar
conrad committed
182 183 184 185 186
}

static void
oi_stats_print (OI_Info * info, OI_Stats * stats, char * label)
{
187
  printf ("\t%s-Length-Maximum: ", label);
188
  ot_fprint_bytes (stdout, stats->length_max);
189 190 191
  putchar ('\n');

  printf ("\t%s-Length-StdDev: ", label);
192
  ot_fprint_bytes (stdout, stats->length_stddev);
193 194 195
  putchar ('\n');

#if 0
conrad's avatar
conrad committed
196 197
  printf ("\t%s-Length-Maximum: %ld bytes\n", label, stats->length_max);
  /*printf ("\t%s-Length-Average: %ld bytes\n", label, stats->length_avg);*/
198
  printf ("\t%s-Length-StdDev: %.0f bytes\n", label, stats->length_stddev);
conrad's avatar
conrad committed
199 200 201 202
  /*
  printf ("\tRange: [%ld - %ld] bytes, Std.Dev. %.3f bytes\n",
	  stats->length_min, stats->length_max, stats->length_stddev);
  */
203
#endif
conrad's avatar
conrad committed
204 205
}

206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
static void
ot_fishead_print(OI_TrackInfo *oit) {
  if (oit->has_fishead) {
    /*
    printf("\tPresentation Time: %.2f\n", (double)oit->fhInfo.ptime_n/oit->fhInfo.ptime_d);
    printf("\tBase Time: %.2f\n", (double)oit->fhInfo.btime_n/oit->fhInfo.btime_d);
    */
    printf("\tSkeleton version: %d.%d\n", oit->fhInfo.version_major, oit->fhInfo.version_minor);
    /*printf("\tUTC: %s\n", oit->fhInfo.UTC);*/
  }
}

static void
ot_fisbone_print(OI_TrackInfo *oit) {

  char *messages, *token;
  
  if (oit->has_fisbone) {
    printf("\n\tExtra information from Ogg Skeleton\n");
    printf("\tserialno: %010d\n", oit->fbInfo.serial_no);
    printf("\tNumber of header packets: %d\n", oit->fbInfo.nr_header_packet);
    printf("\tGranule rate: %.2f\n", (double)oit->fbInfo.granule_rate_n/oit->fbInfo.granule_rate_d);
    printf("\tStart granule: %" PRId64 "\n", oit->fbInfo.start_granule);
    printf("\tPreroll: %d\n", oit->fbInfo.preroll);
    messages = token = _ogg_calloc(oit->fbInfo.current_header_size+1, sizeof(char));
    strcpy(messages, oit->fbInfo.message_header_fields);
    printf("\tMessage Header Fields:\n");
    while (1) {
      token = strsep(&messages, "\n\r");
      printf("\t %s\n", token);
      if (messages == NULL)
	break;
    }
  }
}

conrad's avatar
conrad committed
242 243 244 245
/* oggzinfo_trackinfo_print() */
static void
oit_print (OI_Info * info, OI_TrackInfo * oit, long serialno)
{
246 247 248 249 250
  if (oit->codec_name) {
    printf ("\n%s: serialno %010ld\n", oit->codec_name, serialno);
  } else {
    printf ("\n???: serialno %010ld\n", serialno);
  }
251
  printf ("\t%ld packets in %ld pages, %.1f packets/page\n",
conrad's avatar
conrad committed
252 253
	  oit->packets.count, oit->pages.count,
	  (double)oit->packets.count / (double)oit->pages.count);
254 255

  if (show_length) {
256
    fputs("\tContent-Length: ", stdout);
257
    ot_fprint_bytes (stdout, oit->pages.length_total);
258
    putchar ('\n');
259 260 261
  }

  if (show_bitrate) {
262 263 264
    fputs ("\tContent-Bitrate-Average: ", stdout);
    ot_print_bitrate (oi_bitrate (oit->pages.length_total, info->duration));
    putchar ('\n');
265
  }
conrad's avatar
conrad committed
266

267
  if (oit->codec_info != NULL) {
268
    fputs (oit->codec_info, stdout);
269 270
  }

271 272 273
  if (show_page_stats) {
    oi_stats_print (info, &oit->pages, "Page");
  }
conrad's avatar
conrad committed
274

275 276 277
  if (show_packet_stats) {
    oi_stats_print (info, &oit->packets, "Packet");
  }
278 279 280 281 282 283 284 285 286

  if (show_extra_skeleton_info && oit->has_fishead) {
    ot_fishead_print(oit);
  }
  if (show_extra_skeleton_info && oit->has_fisbone) {
    ot_fisbone_print(oit);
  }

 }
conrad's avatar
conrad committed
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328

static void
oi_stats_average (OI_Stats * stats)
{
  stats->length_avg = stats->length_total / stats->count;
}

static void
oit_calc_average (OI_Info * info, OI_TrackInfo * oit, long serialno)
{
  oi_stats_average (&oit->pages);
  oi_stats_average (&oit->packets);
}

static void
oi_stats_stddev (OI_Stats * stats)
{
  double variance;

  variance = (double)stats->length_deviation_total / (double)(stats->count - 1);
  stats->length_stddev = sqrt (variance);

}

static void
oit_calc_stddev (OI_Info * info, OI_TrackInfo * oit, long serialno)
{
  oi_stats_stddev (&oit->pages);
  oi_stats_stddev (&oit->packets);
}

static int
read_page_pass1 (OGGZ * oggz, const ogg_page * og, long serialno, void * user_data)
{
  OI_Info * info = (OI_Info *)user_data;
  OI_TrackInfo * oit;
  long bytes;

  oit = oggz_table_lookup (info->tracks, serialno);
  if (oit == NULL) {
    oit = oggzinfo_trackinfo_new ();
    oggz_table_insert (info->tracks, serialno, oit);
andre's avatar
andre committed
329 330
  }

conrad's avatar
conrad committed
331
  if (ogg_page_bos ((ogg_page *)og)) {
332
    oit->codec_name = ot_page_identify (oggz, og, &oit->codec_info);
conrad's avatar
conrad committed
333 334
  }

conrad's avatar
conrad committed
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
  bytes = og->header_len + og->body_len;

  /* Increment the total stream length */
  info->length_total += bytes;

  /* Increment the page statistics */
  oit->pages.count++;
  oit->pages.length_total += bytes;
  if (bytes < oit->pages.length_min)
    oit->pages.length_min = bytes;
  if (bytes > oit->pages.length_max)
    oit->pages.length_max = bytes;

  return 0;
}

static int
read_page_pass2 (OGGZ * oggz, const ogg_page * og, long serialno, void * user_data)
{
  OI_Info * info = (OI_Info *)user_data;
  OI_TrackInfo * oit;
  long bytes, deviation;

  oit = oggz_table_lookup (info->tracks, serialno);

  /* Increment the page length deviation squared total */
  bytes = og->header_len + og->body_len;
  deviation = bytes - oit->pages.length_avg;
  oit->pages.length_deviation_total += (deviation * deviation);

  return 0;
}

static int
read_packet_pass1 (OGGZ * oggz, ogg_packet * op, long serialno,
		   void * user_data)
{
  OI_Info * info = (OI_Info *)user_data;
  OI_TrackInfo * oit;

  oit = oggz_table_lookup (info->tracks, serialno);

  /* Increment the packet statistics */
  oit->packets.count++;
  oit->packets.length_total += op->bytes;
  if (op->bytes < oit->packets.length_min)
    oit->packets.length_min = op->bytes;
  if (op->bytes > oit->packets.length_max)
    oit->packets.length_max = op->bytes;

385 386 387 388 389 390 391 392 393 394 395
  if (!op->e_o_s && !memcmp(op->packet, FISBONE_IDENTIFIER, 8)) {
    fisbone_packet fp = fisbone_from_ogg(op);
    oit = oggz_table_lookup (info->tracks, fp.serial_no);
    oit->has_fisbone = 1;
    oit->fbInfo = fp;
  } else if (!op->e_o_s && !memcmp(op->packet, FISHEAD_IDENTIFIER, 8)) {
    fishead_packet fp = fishead_from_ogg(op);
    oit->has_fishead = 1;
    oit->fhInfo = fp;    
  }

conrad's avatar
conrad committed
396 397 398 399 400 401 402 403 404 405
  return 0;
}

static int
read_packet_pass2 (OGGZ * oggz, ogg_packet * op, long serialno,
		   void * user_data)
{
  OI_Info * info = (OI_Info *)user_data;
  OI_TrackInfo * oit;
  long deviation;
406
  
conrad's avatar
conrad committed
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
  oit = oggz_table_lookup (info->tracks, serialno);

  /* Increment the packet length deviation squared total */
  deviation = op->bytes - oit->packets.length_avg;
  oit->packets.length_deviation_total += (deviation * deviation);

  return 0;
}

static int
oi_pass1 (OGGZ * oggz, OI_Info * info)
{
  long n;

  oggz_seek (oggz, 0, SEEK_SET);
  oggz_set_read_page (oggz, -1, read_page_pass1, info);
  oggz_set_read_callback (oggz, -1, read_packet_pass1, info);

conrad's avatar
conrad committed
425
  while ((n = oggz_read (oggz, READ_BLOCKSIZE)) > 0);
conrad's avatar
conrad committed
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440

  oggzinfo_apply (oit_calc_average, info);

  return 0;
}

static int
oi_pass2 (OGGZ * oggz, OI_Info * info)
{
  long n;

  oggz_seek (oggz, 0, SEEK_SET);
  oggz_set_read_page (oggz, -1, read_page_pass2, info);
  oggz_set_read_callback (oggz, -1, read_packet_pass2, info);

conrad's avatar
conrad committed
441
  while ((n = oggz_read (oggz, READ_BLOCKSIZE)) > 0);
conrad's avatar
conrad committed
442 443 444

  oggzinfo_apply (oit_calc_stddev, info);

andre's avatar
andre committed
445 446 447
  return 0;
}

448 449 450 451 452 453
static void
oit_delete (OI_Info * info, OI_TrackInfo * oit, long serialno)
{
  if (oit->codec_info) free (oit->codec_info);
}

andre's avatar
andre committed
454 455 456
int
main (int argc, char ** argv)
{
457 458 459 460 461 462 463
  int show_version = 0;
  int show_help = 0;

  char * progname;
  int i;
  int show_all = 0;

464
  int many_files = 0;
465
  char * infilename;
andre's avatar
andre committed
466
  OGGZ * oggz;
conrad's avatar
conrad committed
467
  OI_Info info;
andre's avatar
andre committed
468

469 470
  progname = argv[0];

andre's avatar
andre committed
471
  if (argc < 2) {
472
    usage (progname);
473
    return (1);
andre's avatar
andre committed
474 475
  }

476
  while (1) {
477
    char * optstring = "hvlbgpka";
478 479 480 481 482 483 484 485 486

#ifdef HAVE_GETOPT_LONG
    static struct option long_options[] = {
      {"help", no_argument, 0, 'h'},
      {"version", no_argument, 0, 'v'},
      {"length", no_argument, 0, 'l'},
      {"bitrate", no_argument, 0, 'b'},
      {"page-stats", no_argument, 0, 'g'},
      {"packet-stats", no_argument, 0, 'p'},
487
      {"skeleton", no_argument, 0, 'k'},
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
      {"all", no_argument, 0, 'a'},
      {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) {
    case 'h': /* help */
      show_help = 1;
      break;
    case 'v': /* version */
      show_version = 1;
      break;
    case 'l': /* length */
      show_length = 1;
      break;
    case 'b': /* bitrate */
      show_bitrate = 1;
      break;
    case 'g': /* page stats */
      show_page_stats = 1;
      break;
    case 'p': /* packet stats */
      show_packet_stats = 1;
      break;
521 522 523
    case 'k': /* extra skeleton info */
      show_extra_skeleton_info = 1;
      break;
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
    case 'a':
      show_all = 1;
      break;
    default:
      break;
    }
  }

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

  if (show_help) {
    usage (progname);
  }

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

  if (optind >= argc) {
    usage (progname);
    goto exit_err;
  }

  if (show_all) {
    show_length = 1;
    show_bitrate = 1;
    show_page_stats = 1;
    show_packet_stats = 1;
554
    show_extra_skeleton_info = 1;
555 556
  }

557 558
  if (argc > optind+1) {
    many_files = 1;
andre's avatar
andre committed
559 560
  }

561 562
  while (optind < argc) {
    infilename = argv[optind++];
conrad's avatar
conrad committed
563

564 565 566 567
    if ((oggz = oggz_open (infilename, OGGZ_READ|OGGZ_AUTO)) == NULL) {
      printf ("unable to open file %s\n", argv[1]);
      return (1);
    }
andre's avatar
andre committed
568

569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
    info.tracks = oggz_table_new ();
    info.length_total = 0;
    
    oi_pass1 (oggz, &info);

    info.duration = oggz_tell_units (oggz);
    
    oi_pass2 (oggz, &info);
    
    oggz_close (oggz);
    
    /* Print summary information */
    if (many_files)
      printf ("Filename: %s\n", infilename);
    fputs ("Content-Duration: ", stdout);
584
    ot_fprint_time (stdout, (double)info.duration / 1000.0);
585 586 587
    putchar ('\n');
    
    if (show_length) {
588
      fputs ("Content-Length: ", stdout);
589
      ot_fprint_bytes (stdout, info.length_total);
590
      putchar ('\n');
591 592 593
    }
    
    if (show_bitrate) {
594 595 596
      fputs ("Content-Bitrate-Average: ", stdout);
      ot_print_bitrate (oi_bitrate (info.length_total, info.duration));
      putchar ('\n');
597
    }
598

599 600 601 602
    oggzinfo_apply (oit_print, &info);
    
    oggzinfo_apply (oit_delete, &info);
    oggz_table_delete (info.tracks);
603

604
    if (optind < argc) puts (SEP);
605
  }
conrad's avatar
conrad committed
606

607 608 609 610 611
 exit_ok:
  exit (0);

 exit_err:
  exit (1);
andre's avatar
andre committed
612
}