/*
 * $Id: mpeg2info.c 560 2005-09-05 20:21:31Z too $
 *
 * Author: Tomi Ollila too ät iki fi
 *
 *	Copyright (c) 2004 Tomi Ollila
 *	    All rights reserved
 *
 * Created: Thu Sep 23 19:28:18 EEST 2004 too
 * Last modified: Mon Sep 05 23:20:48 EEST 2005 too
 *
 * This program is licensed under the GPL v2. See file COPYING for details.
 */

#define VER "1.0"
#define VERDATE "2005/03/16"

/*
 * To compile this, enter `sh mpeg2info.c'.
 */

/*
 * See http://dvd.sourceforge.net/dvdinfo/mpeghdrs.html
 *
 * Thanks also to mpeg2enc, bbinfo, mpegcat, python_mpeg and so on...
 */

#include <stdio.h> /* for NULL */
#include <string.h> /* for memmove() */
#include <unistd.h> /* for read() */
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define ud(x) typedef unsigned char u ## x
ud(1); ud(2); ud(3); ud(4); ud(5); ud(6); ud(7); ud(8);
#undef ud

#if 0
void __cyg_profile_func_enter(void *this_fn, void *call_site)
  __attribute ((no_instrument_function));
void __cyg_profile_func_enter(void *this_fn, void *call_site)
{
  write(1, "enter\n", 6);
}
void __cyg_profile_func_exit(void *this_fn, void *call_site)
  __attribute ((no_instrument_function));
void __cyg_profile_func_exit(void *this_fn, void *call_site)
{
  write(1, "exit\n", 5);
}
#endif

#define DBGS 0

#define D(x, y)

typedef enum { false = 0, true = 1 } bool;

#if (__GNUC__ >= 3)
#define GCCATTR_PRINTF(m, n) __attribute__ ((format (printf, m, n)))
#define GCCATTR_UNUSED   __attribute ((unused))
#define GCCATTR_NORETURN __attribute ((noreturn))
#define GCCATTR_CONST    __attribute ((const))
#else
#define GCCATTR_PRINTF(m, n)
#define GCCATTR_UNUSED
#define GCCATTR_NORETURN
#define GCCATTR_CONST
#endif

typedef struct _ZeroZeroOneBuf ZeroZeroOneBuf;
struct _ZeroZeroOneBuf
{
#define uchar unsigned char
  /* public output variables -- read only (writing these will break things) */
  uchar * data;
  int	  len;
  off_t	  pos;
  /* private */
  struct { /* c_* variables constant after initialized */
    int	    c_fd;
    uchar * c_buf;	/* pointer to start of whole buffer area */
    int	    c_buflen;	/* bytes of whole buffer area */
    int	    len;	/* bytes of active data in buffer */
  } p;
#undef uchar
};

#if DBGS
static char * printzzob(ZeroZeroOneBuf * zb)
/* ; to avoid prototype generation */
{
  static char buf[128];
  sprintf(buf, "{%x, %d -> %d}", mb->data - mb->p.c_buf, mb->p.len, mb->len);
  return buf;
}
#define PMD printzzob(zzob)
#endif

static bool s__fillbuf(ZeroZeroOneBuf * zzob);
static unsigned char * s__memmem(const unsigned char * s, size_t l,
				 const unsigned char * b, size_t bl);

static bool s__fillbuf(ZeroZeroOneBuf * zzob)
{
  int l;

  unsigned char * bufend = zzob->p.c_buf + zzob->p.c_buflen;
  unsigned char * rpos = zzob->data + zzob->p.len;

  D(1, ("s__fillbuf(%s)\n", PMD));

  if (rpos - bufend >= 0)
    {
      if (zzob->p.len == zzob->p.c_buflen)
	return false; /* raise BufferFullException */

      if (zzob->p.len)
	memmove(zzob->p.c_buf, zzob->data, zzob->p.len);
      zzob->data = zzob->p.c_buf;
      rpos = zzob->data + zzob->p.len;
    }
  l = read(zzob->p.c_fd, rpos, bufend - rpos /*> 3? 3: bufend - rpos*/);
  if (l <= 0)
    return false; /* raise EOFException */

  zzob->p.len += l;
  return true;
}

/* Used when \0\0\1 not found in first full buf... */
static bool zzob_init__searchmore(ZeroZeroOneBuf * zzob)
{
  if (zzob->p.len == zzob->p.c_buflen)
    {
      int i = zzob->p.len - 2;
      memcpy(zzob->p.c_buf, zzob->p.c_buf + i, 2);
      zzob->data = zzob->p.c_buf;
      zzob->pos += i;
      zzob->p.len = 2;
      return true;
    }
  else
    return false;
}

bool zzob_init(ZeroZeroOneBuf * zzob, int fd, unsigned char * buf, int buflen)
{
  unsigned char * data;

  zzob->p.c_fd     = fd;
  zzob->p.c_buf    = buf;
  zzob->p.c_buflen = buflen;

  zzob->p.len      = 0;

  zzob->pos  = 0;
  zzob->data = buf;
  zzob->len  = 0;

  D(1, ("zzob_init(fd=%d, %s)\n", fd, PMD));

  while (true)
    {
      if (s__fillbuf(zzob))
	{
	  data = s__memmem(zzob->data, zzob->p.len,
			   (const unsigned char *)"\000\000\001", 3);
	  if (data != NULL)
	    {
	      zzob->pos += data - zzob->data;
	      zzob->p.len -= data - zzob->data;
	      zzob->data = data;
	      return true;
	    }
	}
      else
	if (!zzob_init__searchmore(zzob))
	  return false;
    }
}

bool zzob_data(ZeroZeroOneBuf * zzob, bool locked)
{
  unsigned char * p;
  int o;

  D(1, ("zzob_data(%s)\n", PMD));

  if (locked == false)
    {
      zzob->p.len -= zzob->len;
      zzob->data += zzob->len;
      zzob->pos += zzob->len;
      zzob->len = 0;
    }

  /* if (locked) outf("locked!\n"); */

  o = zzob->len + 3;

  while (true)
    {
      if (zzob->p.len - o > 0
	  && (p = s__memmem(zzob->data + o, zzob->p.len - o,
			    (const unsigned char *)"\000\000\001", 3)) != NULL)
	break;

      if (! s__fillbuf(zzob))
	{
	  D(1,
	    ("s__fillbuf(zzob, zzob->p.len) returned false (%s).\n", PMD));
	  if (locked || zzob->p.len < 4)
	    return false;

	  p = zzob->data + zzob->p.len;
	  break;
	}
    }

  zzob->len = p - zzob->data;

  D(1, ("zzob_data end: %s\n", PMD));
  return true;
}

/* --- util funcs --- */

static unsigned char * s__memmem(const unsigned char * s, size_t l,
				 const unsigned char * b, size_t bl)
{
  const unsigned char * ss = s;

  D(1, ("s__memmem(l = %d)\n", l));

  while ((s = memchr(ss, b[0], l)) != NULL)
    {
      l -= (s - ss);
      D(0, ("s - ss = %d, l = %d\n", s - ss, l));

      if (l < bl)
	return NULL;

      if (memcmp(s, b, bl) == 0)
	{
	  D(1, ("s\n"));
	  return (unsigned char *)s; /* unavoidable compiler warnings !!! */
	}
      ss = s + 1;
      l--;
    }
  return NULL;
}

struct
{
  char * prgname;
  int gops;
  int pictures;
  int seconds;
  bool flush;
  u8 print_now;
  struct {
    bool print_all;
    bool print_details;
    bool print_matrixes;
    bool filter_picture;
    bool filter_slices;
    bool filter_b_hdrs;
    bool filter_audio_pes;
    bool filter_video_pes;
  } o;
} G;

static void init_G(char ** argv)
{
  memset(&G, 0, sizeof G);
  G.prgname = argv[0];
}

static void set_filter(char * str, char chr, bool * vp)
{
  if (strchr(str, chr) != NULL) *vp = true;
}

static void set_filters(char * str)
{
  set_filter(str, 'p', &G.o.filter_picture);
  set_filter(str, 's', &G.o.filter_slices);
  set_filter(str, 'b', &G.o.filter_b_hdrs);
  set_filter(str, 'a', &G.o.filter_audio_pes);
  set_filter(str, 'v', &G.o.filter_video_pes);
}

static bool display(bool hdr, int hdrtype)
{
  if (G.print_now) {
    G.print_now--;
    return true;
  }

  if (hdrtype == 0)
    /*		*/ { if (G.o.filter_picture) return false; }
  else if (hdrtype < 0xb0)
    /*		*/ { if (G.o.filter_slices) return false; }
  else if (hdrtype >= 0xb0 && hdrtype < 0xc0)
    /*		*/ { if (G.o.filter_b_hdrs) return false; }
  else if (hdrtype >= 0xc0 && hdrtype < 0xe0)
    /*		*/ { if (G.o.filter_audio_pes) return false; }
  else if (hdrtype >= 0xe0 && hdrtype < 0xf0)
    /*		*/ { if (G.o.filter_video_pes) return false; }

  if (hdr && (G.o.print_all || G.o.print_details))
    return true;
  if (G.o.print_details)
    return true;

  return false;
}

#define chklen(l) if (len < l) return

#define pos zzob->pos
#define len zzob->len
#define data zzob->data

#define hdrname(args) \
  do { printf("%9lld %4d %02x: ", pos, len, (unsigned char)data[3]); \
       printf args; puts("."); G.flush = true; } while (0)

#define ifchanged_simple(var, txt) \
  if (prev.var != v.var) { G.print_now = 2;\
    printf("\t*** Changed: " txt " (prev %d). (gop %d, pic %d).\n", \
	   prev.var, G.gops, G.pictures); }

#define ifchanged_simple2(var, txt) \
  if (prevp->var != v.var) { G.print_now = 2;\
    printf("\t*** Changed: " txt " (prev %d). (gop %d, pic %d).\n", \
	   prevp->var, G.gops, G.pictures); }


#define changed(args) \
  do { printf("\t*** Changed: "); printf args; \
    printf(". (gop %d, pic %d).\n", G.gops, G.pictures); G.print_now = 2; \
  } while (0)

#define set_if_first(n) if (first) \
    { first = false; memset(&prev, 0, sizeof prev); G.print_now = n; }


static void mpgcode_b2(ZeroZeroOneBuf * zzob)
{
  static bool first = true; if (first) { first = false; G.print_now = 1; }
  if (display(true, 0xb2))
    hdrname(("user data"));
}


struct matrix_info
{
  const char * name;
  const char matrix[64]; /* in zigzag form */
};
const struct {
  struct matrix_info intra[4];
  struct matrix_info inter[3];
} known_matrixes = {
  /* Contents from tables.c of mjpegtools/mpeg2enc */
  {{ "kvcd notch", {
	8,  9,  9,  12, 10, 12, 22, 14, 14, 22, 26, 26, 18, 26, 26, 27,
	27, 27, 27, 27, 27, 29, 29, 29, 31, 29, 29, 29, 34, 34, 34, 36,
	36, 34, 34, 34, 37, 37, 37, 39, 37, 37, 37, 38, 38, 38, 38, 38,
	38, 40, 40, 40, 40, 40, 48, 48, 48, 48, 58, 58, 58, 69, 69, 79 }},
   { "default", {
       8,  16, 16, 19, 16, 19, 22, 22, 22, 22, 22, 22, 26, 24, 26, 27,
       27, 27, 26, 26, 26, 26, 27, 27, 27, 29, 29, 29, 34, 34, 34, 29,
       29, 29, 27, 27, 29, 29, 32, 32, 34, 34, 37, 38, 37, 35, 35, 34,
       35, 38, 38, 40, 40, 40, 48, 48, 46, 46, 56, 56, 58, 69, 69, 83 }},
   { "hires", {
       8,  16, 16, 18, 16, 18, 20, 20, 20, 20, 21, 21, 22, 23, 24, 25,
       25, 24, 23, 23, 23, 24, 24, 24, 24, 26, 26, 26, 30, 30, 28, 26,
       25, 25, 25, 25, 26, 26, 28, 28, 28, 29, 30, 31, 31, 30, 30, 29,
       28, 29, 29, 30, 30, 31, 33, 33, 31, 31, 34, 34, 36, 38, 38, 42 }},
   { "tmpgenc", {
       8,  16, 16, 19, 16, 19, 22, 22, 22, 22, 22, 22, 26, 24, 26, 27,
       27, 27, 26, 26, 26, 26, 27, 27, 27, 29, 29, 29, 34, 34, 34, 29,
       29, 29, 27, 27, 29, 29, 32, 32, 34, 34, 37, 38, 37, 35, 35, 34,
       35, 38, 38, 40, 40, 40, 48, 40, 46, 46, 56, 56, 58, 69, 69, 83 }}
  },
  {{ "kvcd notch", {
	16, 18, 18, 20, 20, 20, 22, 22, 22, 22, 24, 24, 24, 24, 24, 26,
	26, 26, 26, 26, 26, 28, 28, 28, 30, 28, 28, 28, 30, 30, 30, 32,
	32, 30, 30, 30, 32, 32, 32, 34, 32, 32, 32, 34, 34, 34, 34, 34,
	34, 36, 36, 36, 36, 36, 38, 38, 38, 38, 40, 42, 40, 42, 42, 44 }},
   { "default", {
       16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
       16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
       16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
       16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 }},
   { "tmpgenc", {
       16, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 20, 21,
       21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23,
       23, 23, 23, 23, 24, 24, 24, 25, 24, 24, 24, 25, 26, 26, 26, 26,
       25, 27, 27, 27, 27, 27, 28, 28, 28, 28, 30, 30, 30, 31, 31, 33 }}
  }
};


static const char * known_matrix(const unsigned char * matrix,
				 const struct matrix_info * matrix_info,
				 int matrix_infos)
{
  int i;

  while (matrix_infos-- > 0)
    {
      for (i = 63; i >= 0; i--)
	if (matrix[i] != matrix_info[matrix_infos].matrix[i])
	  break;
      if (i < 0)
	return matrix_info[matrix_infos].name;
    }
  return " + ";
}

static void print_matrix(const unsigned char * matrix)
{
  int i; int j;
  for (i = 0; i < 4; i++) {
    printf("\t\t");
    for (j = 0; j < 16; j++)
      printf(" %02d", matrix[i * 16 + j]);
    printf("\n");
  }
}

static void mpgcode_b3(ZeroZeroOneBuf * zzob)
{
  static bool first = true;
  struct vst {
    int horizontal_size;
    int vertical_size;
    u4 aspect_ratio;
    u4 frame_rate;
    int bitrate;
    int vbv_buffer_size;
    u1 constrained_parameters_flag;
    u1 load_intra_quantiser_matrix;
    u1 load_non_intra_quantiser_matrix;
  };
  static struct vst prev;
  struct vst v;

  unsigned char * p;
  /* matrixes, in zig-zag form. */
  unsigned char intra_mzz[64], inter_mzz[64];
  static unsigned char prev_intra_mzz[64], prev_inter_mzz[64];
  const char * istr = " - ", *nistr = istr;
  int i, hdfm;

  v.horizontal_size = (data[4] << 4) + (data[5] >> 4);
  v.vertical_size = ((data[5] & 0x0f) << 8) + data[6];
  v.aspect_ratio = data[7] >> 4;
  v.frame_rate = data[7] & 0x0f;
  v.bitrate = (data[8] << 10) + (data[9] << 2) + (data[10] >> 6);
  v.vbv_buffer_size = (data[10] << 5) + (data[11] >> 3);
  v.constrained_parameters_flag = !! (data[11] & 0x04);

  p = data + 11;
  v.load_intra_quantiser_matrix = !! (*p & 0x02);
  if (v.load_intra_quantiser_matrix)
    {
      for (i = 0; i < 64; i++, p++)
	intra_mzz[i] = ((*p & 0x01) << 7) | (p[1] >> 1);
      istr = known_matrix(intra_mzz, known_matrixes.intra,
			  sizeof known_matrixes.intra
			  / sizeof known_matrixes.intra[0]);
    }

  v.load_non_intra_quantiser_matrix = !! (*p++ & 0x01);
  if (v.load_non_intra_quantiser_matrix)
    {
      for (i = 0; i < 64; i++)
	inter_mzz[i] = *p++;
      nistr = known_matrix(inter_mzz, known_matrixes.inter,
			   sizeof known_matrixes.inter
			   / sizeof known_matrixes.inter[0]);
    }

  set_if_first(2)
  else {
    ifchanged_simple(horizontal_size, "horizontal size");
    ifchanged_simple(vertical_size, "vertical size");
    ifchanged_simple(aspect_ratio, "aspect ratio");
    ifchanged_simple(frame_rate, "frame rate");
    ifchanged_simple(load_intra_quantiser_matrix,
		     "load intra quantiser matrix");
    if (v.load_intra_quantiser_matrix
	&& memcmp(prev_intra_mzz, intra_mzz, 64) != 0)
      changed(("intra matrix"));

    if (v.load_non_intra_quantiser_matrix
	&& memcmp(prev_inter_mzz, inter_mzz, 64) != 0)
      changed(("non-intra matrix"));

    ifchanged_simple(load_non_intra_quantiser_matrix,
		     "load non-intra quantiser matrix");
#if 0
    if (prev.horizontal_size != v.horizontal_size)
      changed(("horizontal size (prev: %d)", prev.horizontal_size));
#endif
  }
  if ((hdfm = display(true, 0xb3)))
    hdrname(("sequence header"));

  if (display(false, 0xb3)) {
    printf("\t  horizontal size: %d\n", v.horizontal_size);
    printf("\t  vertical size: %d\n", v.vertical_size);
    printf("\t  aspect ratio: %d\n", v.aspect_ratio);
    printf("\t  frame rate: %d\n", v.frame_rate);
    printf("\t  bit rate: %d\n", v.bitrate);
    printf("\t  VBV buffer size: %d\n", v.vbv_buffer_size);
    printf("\t  constrained parameters flag: %d\n",
	   v.constrained_parameters_flag);
    printf("\t  load intra quantiser matrix: %d (%s)\n",
	   v.load_intra_quantiser_matrix, istr);
    hdfm &= G.o.print_matrixes;
    if (hdfm && v.load_intra_quantiser_matrix) print_matrix(intra_mzz);
    printf("\t  load non-intra quantiser matrix: %d (%s)\n",
	   v.load_non_intra_quantiser_matrix, nistr);
    if (hdfm && v.load_non_intra_quantiser_matrix) print_matrix(inter_mzz);

    putchar('\n'); G.flush = true;
  }

  if (v.load_intra_quantiser_matrix) memcpy(prev_intra_mzz,intra_mzz,64);
  if (v.load_non_intra_quantiser_matrix) memcpy(prev_inter_mzz,inter_mzz,64);

  prev = v;
}

static void mpgcode_b4(ZeroZeroOneBuf * zzob)
{
  hdrname(("sequence error"));
}

static void mpgcode_b5(ZeroZeroOneBuf * zzob)
{
  unsigned char * p;

  switch (data[4] & 0xf0)
    {
    case 0x10:
      {
	static bool first = true;
	struct vst {
	  u8 profile_and_level;
	  u1 progressive_sequence;
	  u2 chroma_format;
	  u2 horizontal_size_extension;
	  u2 vertical_size_extension;
	  int bit_rate_extension;
	  u8 vbv_buffer_size_extension;
	  u1 low_delay;
	  u2 frame_rate_extension_n;
	  u5 frame_rate_extension_d;
	};
	static struct vst prev;
	struct vst v;

	v.profile_and_level = ((data[4] & 0x0f) << 4) + (data[5] >> 4);
	v.progressive_sequence = !! (data[5] & 0x08);
	v.chroma_format =  (data[5] & 0x06) >> 1;
	v.horizontal_size_extension = ((data[5] & 0x01) << 1) + (data[6] >> 7);
	v.vertical_size_extension = (data[6] & 0x60) >> 5;
	v.bit_rate_extension = ((data[6] & 0x1f) << 7) + (data[7] >> 1);
	v.vbv_buffer_size_extension = data[8];
	v.low_delay = !! (data[9] & 0x80);
	v.frame_rate_extension_n = (data[9] & 0x60) >> 5;
	v.frame_rate_extension_d = data[9] & 0x1f;

	set_if_first(2)
	else {
	  ifchanged_simple(profile_and_level, "profile and level");
	  ifchanged_simple(progressive_sequence, "progressive sequence");
	  ifchanged_simple(chroma_format, "chroma format");
	  ifchanged_simple(horizontal_size_extension,
			   "horizontal size extension");
	  ifchanged_simple(vertical_size_extension,
			   "vertical_size_extension");
	  ifchanged_simple(bit_rate_extension, "bit_rate_extension");
	  /*ifchanged_simple(); */
	  /*ifchanged_simple(); */
	  /*ifchanged_simple(); */
	}

	if (display(true, 0xb5))
	  hdrname(("1: sequence extension"));

	if (display(false, 0xb5)) {
	  printf("\t  profile and level: %d\n", v.profile_and_level);
	  printf("\t  progressive sequence: %d\n", v.progressive_sequence);
	  printf("\t  chroma format: %d\n", v.chroma_format);
	  printf("\t  horizontal size extension: %d\n",
		 v.horizontal_size_extension);
	  printf("\t  vertical size extension: %d\n",
		 v.vertical_size_extension);
	  printf("\t  bit rate extension: %d\n", v.bit_rate_extension);
	  printf("\t  vbv buffer size extension: %d\n",
		 v.vbv_buffer_size_extension);
	  printf("\t  low delay: %d\n", v.low_delay);
	  printf("\t  frame rate extension n: %d\n", v.frame_rate_extension_n);
	  printf("\t  frame rate extension d: %d\n", v.frame_rate_extension_d);

	  putchar('\n'); G.flush = true;
	}
	prev = v;
      }
      break;

    case 0x20:
      {
	static bool first = true;
	struct vst {
	  u3 video_format;
	  u1 color_description_flag;
	  u8 color_primaries;
	  u8 transfer_characteristics;
	  u8 matrix_coefficients;
	  int display_horizontal_size;
	  int display_vertical_size;
	};
	static struct vst prev;
	struct vst v;

	v.video_format = (data[4] & 0x0e) >> 1;
	v.color_description_flag = !! (data[4] & 0x01);
	if (v.color_description_flag)
	  {
	    v.color_primaries = data[5];
	    v.transfer_characteristics = data[6];
	    v.matrix_coefficients = data[7];
	    p = data + 8;
	  }
	else
	  p = data + 5;

	v.display_horizontal_size = (p[0] << 6) + (p[1] >> 2);
	v.display_vertical_size
	  = ((p[1] & 0x01) << 13) + (p[2] << 5) + (p[3] >> 3);

	set_if_first(2)
	else {
	  ifchanged_simple(video_format, "video format");
	  ifchanged_simple(display_horizontal_size,
			   "display_horizontal_size");
	  ifchanged_simple(display_vertical_size, "display_vertical_size");
	}

	if (display(true, 0xb5))
	  hdrname(("2: sequence display extension"));

	if (display(false, 0xb5)) {
	  printf("\t  video format: %d\n", v.video_format);
	  printf("\t  color description flag: %d\n", v.color_description_flag);
	  if (v.color_description_flag)
	    {
	      printf("\t  - color primaries: %d\n", v.color_primaries);
	      printf("\t  - transfer characteristics %d\n",
		     v.transfer_characteristics);
	      printf("\t  - matrix coefficients: %d\n", v.matrix_coefficients);
	    }
	  printf("\t  display horizontal size: %d\n",
		 v.display_horizontal_size);
	  printf("\t  display vertical size: %d\n", v.display_vertical_size);

	  putchar('\n'); G.flush = true;
	}
	prev = v;
      }
      break;

    case 0x80:
      {
	static bool first = true;
	struct vst {
	  u4 f_code_fh;
	  u4 f_code_fv;
	  u4 f_code_bh;
	  u4 f_code_bv;
	  u2 intra_dc_precision;
	  u2 picture_structure;
	  u1 top_field_first;
	  u1 frame_pred_frame_dct;
	  u1 concealment_motion_vectors;
	  u1 q_scale_type;
	  u1 intra_vlc_format;
	  u1 alternate_scan;
	  u1 repeat_first_field;
	  u1 chroma_420_type;
	  u1 progressive_frame;
	  u1 composite_display;
	  /* more to follow, but ignored */
	};
	static struct vst prev;
	struct vst v;

	v.f_code_fh = data[4] & 0x0f;
	v.f_code_fv = data[5] >> 4;
	v.f_code_bh = data[5] & 0x0f;
	v.f_code_bv = data[6] >> 4;
	v.intra_dc_precision = (data[6] >> 2) & 0x03;
	v.picture_structure = data[6] & 0x03;
	v.top_field_first = !!(data[7] & 0x80);
	v.frame_pred_frame_dct = !!(data[7] & 0x40);
	v.concealment_motion_vectors = !!(data[7] & 0x20);
	v.q_scale_type = !!(data[7] & 0x10);
	v.intra_vlc_format = !!(data[7] & 0x08);
	v.alternate_scan = !!(data[7] & 0x04);
	v.repeat_first_field = !!(data[7] & 0x02);
	v.chroma_420_type = data[7] & 0x01;
	v.progressive_frame = !!(data[8] & 0x80);
	v.composite_display = !!(data[8] & 0x40);
	/* if (composite_display) ... */

	set_if_first(2)
	else {
	  /*ifchanged_simple(f_code_fh, "f_code[0][0]"); */
	  /* ... */
	  ifchanged_simple(intra_dc_precision, "intra dc precision");
	  ifchanged_simple(picture_structure, "picture structure");
	  ifchanged_simple(top_field_first, "top field first");
	  /*ifchanged_simple(frame_pred_frame_dct, "frame pred frame dct"); */
	  ifchanged_simple(concealment_motion_vectors,
			   "concealment motion vectors");
	  ifchanged_simple(intra_vlc_format, "intra vlc format");
	  ifchanged_simple(alternate_scan, "alternate scan");
	  ifchanged_simple(repeat_first_field, "repeat first field");
	  ifchanged_simple(chroma_420_type, "chroma 420 type");
	  ifchanged_simple(progressive_frame, "progressive frame");
	  ifchanged_simple(composite_display, "composite display");
	}

	if (display(true, 0xb5))
	  hdrname(("8: picture coding extension"));

	if (display(false, 0xb5)) {
	  printf("\t  f_code[0][0]: %d, f_code[0][1]: %d"
		 " f_code[1][0]: %d, f_code[1][1]: %d\n",
		 v.f_code_fh, v.f_code_fv, v.f_code_bh, v.f_code_bv);
	  printf("\t  intra dc precision: %d\n", v.intra_dc_precision);
	  printf("\t  picture structure: %d\n", v.picture_structure);
	  printf("\t  top field first: %d\n", v.top_field_first);
	  printf("\t  frame pred frame dct: %d\n", v.frame_pred_frame_dct);
	  printf("\t  concealment motion vectors: %d\n",
		 v.concealment_motion_vectors);
	  printf("\t  intra vlc format: %d\n", v.intra_vlc_format);
	  printf("\t  alternate scan: %d\n", v.alternate_scan);
	  printf("\t  repeat first field: %d\n", v.repeat_first_field);
	  printf("\t  chroma 420 type: %d\n", v.chroma_420_type);
	  printf("\t  progressive frame: %d\n", v.progressive_frame);
	  printf("\t  composite display: %d\n", v.composite_display);

	  putchar('\n'); G.flush = true;
	}
	prev = v;
      }
      break;
    }
}

static void mpgcode_b7(ZeroZeroOneBuf * zzob)
{
  static bool first = true; if (first) { first = false; G.print_now = 1; }
  if (display(true, 0xb7))
    hdrname(("sequence end"));
}

static void mpgcode_b8(ZeroZeroOneBuf * zzob)
{
  static bool first = true;
  struct vst {
    int drop_frame_flag;
    int hour, minute, second, frame;
    int closed_gop, broken_gop;
    unsigned long xtime;
  };
  static struct vst prev;
  struct vst v;

  chklen(7);

  v.drop_frame_flag = !! (data[4] & 0x80);
  v.hour = (data[4] & 0x7c) >> 2;
  v.minute = ((data[4] & 0x03) << 4) + (data[5] >> 4);
  v.second = ((data[5] & 0x07) << 3) + (data[6] >> 5);
  v.frame = ((data[6] & 0x1f) << 1) + (data[7] >> 7);

  v.xtime = (v.hour * 60 + v.minute) * 60 + v.second;

  v.closed_gop = !! (data[7] & 0x40);
  v.broken_gop = !! (data[7] & 0x20);

  set_if_first(2)
  else {
    ifchanged_simple(broken_gop, "broken gop");
    if (v.xtime != prev.xtime) {
      G.seconds++;
      if (v.xtime - prev.xtime > 2) {
	changed(("unexpected time jump; prev: %02d:%02d:%02d.%02d",
		 prev.hour, prev.minute, prev.second, prev.frame));
      }
    }
  }

  G.gops++;
  if (display(true, 0xb8))
    hdrname(("GOP header (#%d)", G.gops));

  if (display(false, 0xb8)) {
    printf("\t  drop: %d, hour: %2d, min: %2d, sec: %2d, frame: %2d, "
	   "closed: %d, broken: %d\n", v.drop_frame_flag,
	   v.hour, v.minute, v.second, v.frame, v.closed_gop, v.broken_gop);

    putchar('\n'); G.flush = true;
  }
  prev = v;
}

static void mpgcode_b9(ZeroZeroOneBuf * zzob)
{
  static bool first = true; if (first) { first = false; G.print_now = 1; }
  if (display(true, 0xb9))
    hdrname(("program end"));
}

/* Pack Header:  00 00 01 ba
 *
 *  byte 4   byte 5   byte 6   byte 7   byte 8   byte 9
 * 76543210 76543210 76543210 76543210 76543210 76543210
 * 01[SCR]1 [ SCR 29..15 ]1[  SCR 14..00   ]1[ SCR_ext]1
 *
 *  byte10   byte11   byte12   byte13
 * 76543210 76543210 76543210 76543210
 * [   Program_Mux_Rate   ]11 [res][psl]
 */

static void mpgcode_ba(ZeroZeroOneBuf * zzob)
{
  static bool first = true;
  struct vst {
    long long scr;
    int scr_ext;
    int program_mux_rate;
    /* int stuffing_length; */
  };
  static struct vst prev;
  struct vst v;
  double scrms;

  chklen(13);

  v.scr = ((long long)(data[4] & 0x38) << 27)
    + ((data[4] & 0x03) << 28) + (data[5] << 20) + ((data[6] & 0xf8) << 12)
    + ((data[6] & 0x03) << 13) + (data[7] << 5) + ((data[8] & 0xf8) >> 3);

  v.scr_ext = ((data[8] & 0x03) << 7) + (data[9] >> 1);

  scrms = (v.scr * 300 + v.scr_ext) / 27000.0;

  v.program_mux_rate = (data[10] << 14) + (data[11] << 6) + (data[12] >> 2);

  set_if_first(2)
  else {
    ifchanged_simple(program_mux_rate, "program mux rate");
  }

  if (display(true, 0xba))
    hdrname(("pack header"));

  if (display(false, 0xba)) {
    printf("\t  SCR: %lld SCR_ext: %d (= %f ms) program mux rate: %d\n",
	   v.scr, v.scr_ext, scrms, v.program_mux_rate);

    putchar('\n'); G.flush = true;
  }
  prev = v;
}

/* 2.5.3.5 in ...
  system header start code     32 bits (0x000001bb)
  header length                16 bits
  marker bit                    1
  rate bound                   22 bits
  marker bit                    1
  audio bound                   6
  fixed flag                    1
  csps flag                     1
  system audio lock flag        1
  system video lock flag        1
  marker bit                    1
  video bound                   5
  packet rate restriction flag  1 bit
  reserved byte                 7
  while nextbit == 1
     stream id                  8
     '1 1'                      2
     P-STD buffer bound scale   1
     P-STD buffer size bound   13

 So interest for this does not break through my inertia threshold just now
*/

static void mpgcode_bb(ZeroZeroOneBuf * zzob)
{
  static bool first = true; if (first) { first = false; G.print_now = 1; }
  if (display(true, 0xbb))
    hdrname(("system header"));
}

/* 2.5.4.1 in above doc. Same comment... */

static void mpgcode_bc(ZeroZeroOneBuf * zzob)
{
  static bool first = true; if (first) { first = false; G.print_now = 1; }
  if (display(true, 0xbc))
    hdrname(("program stream map"));
}


static void mpgcode_bd(ZeroZeroOneBuf * zzob)
{
  static bool first = true; if (first) { first = false; G.print_now = 1; }
  if (display(true, 0xbd))
    hdrname(("private stream 1 (non MPEG audio, subpictures)"));
}

static void mpgcode_be(ZeroZeroOneBuf * zzob)
{
  static bool first = true; if (first) { first = false; G.print_now = 1; }
  if (display(true, 0xbe))
    hdrname(("padding stream"));
}

static void mpgcode_bf(ZeroZeroOneBuf * zzob)
{
  static bool first = true; if (first) { first = false; G.print_now = 1; }
  if (display(true, 0xbf))
    hdrname(("private stream 2 (navigation data)"));
}


static void pes_header(ZeroZeroOneBuf * zzob, const char * hdrnam)
{
  unsigned char * dp;

  static bool first_a = true;
  static bool first_v = true;
  struct vst {
    int pes_packet_length;
    u2 pes_scrambling_control;
    u1 pes_priority;
    u1 data_alignment_indicator;
    u1 copyright;
    u1 original_or_copy;
    u2 pts_dts_flags;
    u1 escr_flag;
    u1 es_rate_flag;
    u1 dsm_trick_mode_flag;
    u1 additional_copy_info_flag;
    u1 pes_crc_flag;
    u1 pes_extension_flag;
    u8 pes_header_data_length;

    long long pts;
    long long dts;
    long long escr_base;
    int escr_ext;
    int es_rate;
  };
  /* this might need more later, if used */
  static struct vst prev_a;
  static struct vst prev_v;
  struct vst * prevp = (data[3] >= 0xe0)? &prev_v: &prev_a;
  bool * firstp = (data[3] >= 0xe0)? &first_v: &first_a;
  struct vst v;

  v.pes_packet_length = data[4] * 256 + data[5];

  v.pes_scrambling_control = (data[6] & 0x30) >> 4;
  v.pes_priority = !!(data[6] & 0x08);
  v.data_alignment_indicator = !!(data[6] & 0x04);
  v.copyright = !!(data[6] & 0x02);
  v.original_or_copy = data[6] & 0x01;

  v.pts_dts_flags = (data[7] & 0xc0) >> 6;
  v.escr_flag = !!(data[7] & 0x20);
  v.es_rate_flag = !!(data[7] & 0x10);
  v.dsm_trick_mode_flag = !!(data[7] & 0x08);
  v.additional_copy_info_flag = !!(data[7] & 0x04);
  v.pes_crc_flag = !!(data[7] & 0x02);
  v.pes_extension_flag = data[7] & 0x01;

  v.pes_header_data_length = data[8];

  dp = data + 9;
  if (v.pts_dts_flags & 0x02)
    {
      v.pts = ((long long)(data[9] & 0x0e) << 29)
	+ (data[10] << 22) + ((data[11] & 0xfe) << 14)
	+ (data[12] << 7) + (data[13] >> 1);
      dp += 5;
    }
  if (v.pts_dts_flags == 0x03)
    {
      v.dts = ((long long)(data[14] & 0x0e) << 29)
	+ (data[15] << 22) + ((data[16] & 0xfe) << 14)
	+ (data[17] << 7) + (data[18] >> 1);
      dp += 5;
    }

#if 0 /* the following data is not verified (ands and shifts) to work */

  if (escr_flag)
    {
      escr = ((long long)(dp[0] & 0x38) << 27) + ((dp[1] & 0x03) << 28)
	+ (dp[1] << 20) + ((dp[2] & 0xf8) << 12) + ((dp[3] & 0x03) << 12)
	+ (dp[3] << 5) + (dp[4] >> 3);
      escr_ext = ((dp[4] & 0x03) << 6) + (dp[5] >> 1);
      dp += 6;
    }
  if (es_rate_flag)
    {
      es_rate = ((dp[0] & 0x7f) << 15) + (dp[1] << 7) + (dp[2] >> 1);
      dp += 3;
    }
  if (dsm_trick_flag)
    {
      ;
    }
  if (additional_copy_info_flag)
    {
      add_copy_info = dp[0] & 0x7f;
      dp += 1;
    }
  /* eek, too much to tune just now -- anyone cares ??? */
  if (f_pes_crc)
    {
      ;
    }
#endif

  if (*firstp)
    { *firstp = false; memset(prevp, 0, sizeof *prevp); G.print_now = 2; }
  else {
    /*ifchanged_simple2(pts, "pts (testing...)") */
    ifchanged_simple2(pes_scrambling_control, "pes scrambling control");
    ifchanged_simple2(pes_priority, "pes priority");
    ifchanged_simple2(data_alignment_indicator, "data alignment indicator");
    ifchanged_simple2(copyright, "copyright");
    ifchanged_simple2(original_or_copy, "original or copy");
    ifchanged_simple2(escr_flag, "escr flag");
    ifchanged_simple2(es_rate_flag, "es rate flag");
    ifchanged_simple2(dsm_trick_mode_flag, "dsm trick mode flag");
    ifchanged_simple2(additional_copy_info_flag, "additional copy info flag");
    ifchanged_simple2(pes_crc_flag, "pes crc flag");
#if 0 /* hits so often... */
    ifchanged_simple2(pes_extension_flag, "pes extension flag");
    ifchanged_simple2(pes_header_data_length, "pes header data length");
#endif
  }

  if (display(true, data[3]))
    hdrname((hdrnam));

  if (display(false, data[3])) {
    printf("\t  pes packet length: %d (> %lld). pts_dts: %d\n",
	   v.pes_packet_length, pos + v.pes_packet_length + 6,
	   v.pts_dts_flags);

    if (v.pts_dts_flags == 0x02)
      printf("\t  pts: %lld (%f ms)\n", v.pts, v.pts / 90.0);
    else if (v.pts_dts_flags == 0x03)
      printf("\t  pts: %lld (%f ms), dts: %lld (%f ms)\n",
	     v.pts, v.pts / 90.0, v.dts, v.dts / 90.0);

    printf("\t  pes scrabling control: %d\n", v.pes_scrambling_control);
    printf("\t  pes priority: %d\n", v.pes_priority);
    printf("\t  data alignment indicator: %d\n", v.data_alignment_indicator);
    printf("\t  copyright: %d\n", v.copyright);
    printf("\t  original or copy: %d\n", v.original_or_copy);
    printf("\t  escr flag: %d\n", v.escr_flag);
    printf("\t  es rate flag: %d\n", v.es_rate_flag);
    printf("\t  dsm trick mode flag: %d\n", v.dsm_trick_mode_flag);
    printf("\t  additional copy info flag: %d\n", v.additional_copy_info_flag);
    printf("\t  pes crc flag: %d\n", v.pes_crc_flag);
    printf("\t  pes extension flag: %d\n", v.pes_extension_flag);
    printf("\t  pes header data length: %d\n", v.pes_header_data_length);

    putchar('\n'); G.flush = true;
  }
  *prevp = v;
}

static void audio_stream(ZeroZeroOneBuf * zzob)
{
  while (len < 8)
    if (! zzob_data(zzob, true))
      return;

  pes_header(zzob, "audio stream");
}

static void video_stream(ZeroZeroOneBuf * zzob)
{
  while (len < 8)
    if (! zzob_data(zzob, true))
      return;

  pes_header(zzob, "video_stream");
}

/* --*-- */

static void picture(ZeroZeroOneBuf * zzob)
{
  static char x[] = "xIPBDxxx";
  static bool first = true;
  struct vst {
    int temperal_sequence_number;
    int frame_type;
    int vbv_delay;
  };
  static struct vst prev;
  struct vst v;

  chklen(7);

  v.temperal_sequence_number = (data[4] << 2) + ((data[5] & 0xc0) >> 6);
  v.frame_type = ((data[5] & 0x38) >> 3);
  v.vbv_delay = ((data[5] & 0x02) << 13)
    + (data[6] << 5) + ((data[7] & 0xf8) >> 3);

  /* XXX vectors omitted (mpeg-1 stuff anyway) */

  set_if_first(2)
  else {
    ifchanged_simple(vbv_delay, "vbv delay");
  }

  G.pictures++;
  if (display(true, 00))
    hdrname(("picture #%d", G.pictures));

  if (display(false, 00)) {
    printf("\t  temperal sequence number: %2d, "
	   "frame type: %d(%c), vbv delay: %d\n", v.temperal_sequence_number,
	   v.frame_type, x[v.frame_type], v.vbv_delay);

    putchar('\n'); G.flush = true;
  }
  prev = v;
}

static void slice(ZeroZeroOneBuf * zzob)
{
  static bool first = true; if (first) { first = false; G.print_now = 1; }
  if (display(true, 01))
    hdrname(("slice"));
}

#undef pos
#undef len
#undef data

/* --*-- */

static void usage(const char * format, ...)
  GCCATTR_PRINTF(1, 2) GCCATTR_NORETURN;
static void usage(const char * format, ...)
{
  if (format)
    {
      va_list ap;
      fprintf(stderr, "\n%s: ", G.prgname);
      va_start(ap, format);
      vfprintf(stderr, format, ap);
      fputs("\n", stderr);
    }

  fprintf(stderr,"\n*** mpeg2info " VER " (" VERDATE ").\n"
	  "\nUsage: %s [-adm] [-f filteropts] filename.\n\n"
	  "\tOptions:\n"
	  "\t\t-a: show all headers.\n"
	  "\t\t-d: show details of headers.\n"
	  "\t\t-m: show quantiser matrixes (in zigzag form).\n"
	  "\t\t-f filteropts: filter some headers out:\n"
	  "\t\t\t chars `s', `p', `b', `a' and `v' are effective here.\n"
	  "\n\tfilename: mpeg file name or `-' for stdin.\n\n", G.prgname);

  exit(1);
}

static char * get_next_arg(int * c, char *** v)
{
  if ((*v)[1] == NULL)
    usage(NULL);
  (*v)++; (*c)--;
  return **v;
}

int main(int argc, char ** argv)
{
  ZeroZeroOneBuf zzob;
  unsigned char buf[65536];
  char * arg;

  /* put stdout in fully buffered mode, we fflush() when needed */
  setvbuf(stdout, NULL, _IOFBF, 0);

  init_G(argv);

  while ((arg = get_next_arg(&argc, &argv))[0] == '-' && arg[1] != '\0')
    {
      char * p = arg + 1;

      for (p = arg + 1; *p; p++)
	{
	  switch (*p)
	    {
	    case 'a': G.o.print_all = 1; break;
	    case 'd': G.o.print_details = 1; break;
	    case 'm': G.o.print_matrixes = 1; break;
	    case 'f': set_filters(get_next_arg(&argc, &argv)); break;
	    default: usage("%c: unknown arg.", *p);
	    }
	}
    }
  if (argc != 1)
    usage(NULL);

  if (arg[0] == '-' && arg[1] == '\0')
    {
      if (! zzob_init(&zzob, 0, buf, sizeof buf))
	return 1;
    }
  else
    {
      int fd;
      if ((fd = open(arg, O_RDONLY)) < 0)
	{
	  fprintf(stderr, "%s: Can not open file %s: %s.\n",
		  G.prgname, arg, strerror(errno));
	  exit(1);
	}
      if (! zzob_init(&zzob, fd, buf, sizeof buf))
	return 1;
    }



  for (; zzob_data(&zzob, false); )
    {
#if 0
      write(1, zzob.data, zzob.len);
      continue;
#endif
#if 0
      if (1 && zzob.data[3] < 128)  continue;
      if (0 && zzob.data[3] != 0xb3) continue;

      printf("pos: %9lld, len: %5d. %02x %02x %02x %02x\n",
	   zzob.pos, zzob.len,
	   zzob.data[0], zzob.data[1], zzob.data[2], zzob.data[3]);
      fflush(stdout);
#endif

      if (zzob.data[3] == 0x00)
	{
	  picture(&zzob);
	}
      else if (zzob.data[3] < 0xb0)
	{
	  slice(&zzob);
	}
      else if (zzob.data[3] >= 0xc0 && zzob.data[3] < 0xe0)
	{
	  audio_stream(&zzob);
	}
      else if (zzob.data[3] >= 0xe0 && zzob.data[3] < 0xf0)
	{
	  video_stream(&zzob);
	}
      else switch (zzob.data[3])
	{
#define callmpgcode(hex) \
	  case 0x ## hex: mpgcode_ ## hex (&zzob); break
	  callmpgcode(b2);
	  callmpgcode(b3);
	  callmpgcode(b4);
	  callmpgcode(b5);
	  callmpgcode(b7);
	  callmpgcode(b8);
	  callmpgcode(b9);
	  callmpgcode(ba);
	  callmpgcode(bb);
	  callmpgcode(bc);
	  callmpgcode(bd);
	  callmpgcode(be);
	  callmpgcode(bf);
	default:
#define   pos zzob.pos
#define   len zzob.len
#define   data zzob.data
	  hdrname(("unhandled start code %x "
		   "(might not be start code at all...)", data[3]));
	}

      if (G.flush) { fflush(stdout); G.flush = false; }
    }

  printf("Summary:\n GOPs %d, pictures %d,", G.gops, G.pictures);
  printf(" seconds ~%d, length %lld (~%d kbs).\n",
	 G.seconds, pos, (int)(pos / G.seconds * 8 / 1000));
  return 0;
}

