/*
#
# MPEG2Parser class testing tool
#
# $Id: mpegcat.c,v 1.20 2003/08/23 18:33:09 nemies Exp $
#
# Copyright (C) 2001-2003 Kees Cook
# kees@outflux.net, http://outflux.net/
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
# http://www.gnu.org/copyleft/gpl.html
#
*/

/*
 * buffer management inspired by mplayer
 */

#include "GOPchop.h"

#include <stdio.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/uio.h>
#include <string.h>
#include <getopt.h>
#include "mpeg2structs.h"
#include "mpeg2consts.h"
#include "file_buffer.h"

// general stuff
int debug = 0;
int dvd_names = 0;

// what packets to process
int enter_video = 0;

// top-level packets to display
int no_pack = 0;
int no_audio = 0;
int no_video = 0;
int no_other_streams = 0;
// top-level headers to display
int no_system_headers = 0;
// top-level other things...
int no_errors = 0;

// video packet display toggles
int no_slices = 1;
int no_sequence = 0;
int no_GOP_headers = 0;
int no_picture_headers = 0;

// file position options
int walk_offsets = 1;
off_t begin_at = 0;
off_t num_bytes = 0;

static struct option long_options[] = {
    {"help", 0, 0, 'h'},
    {"debug", 0, 0, 'd'},
    {"dvd-format", 0, 0, 'D'},
    {"errors", 0, 0, 'e'},
    {"enter-video", 0, 0, 'V'},
    {"slices", 0, 0, 's'},
    {"audio", 0, 0, 'a'},
    {"video", 0, 0, 'v'},
    {"pack", 0, 0, 'p'},
    {"sequence", 0, 0, 'q'},
    {"system", 0, 0, 'y'},
    {"other", 0, 0, 'o'},
    {"just-frames", 0, 0, 'F'},
    {"GOPs", 0, 0, 'g'},
    {"frames", 0, 0, 'f'},
    {"invert", 0, 0, 'i'},
    {"scan-stream", 0, 0, 't'},
    {"begin", 0, 0, 'b'},
    {"num", 0, 0, 'n'},
    {"write", 0, 0, 'w'},
    {NULL, 0, 0, 0}
};
static char *short_options = "h?dDeVsavpqyoFgfitb:n:w:";

void usage(char *argv[])
{
    printf("Usage: %s [options ...] MPEG2-PS ...\n", argv[0]);
    printf("\n");

    printf("Processing Toggles\n");
    printf("-V/--enter-video  Process video packets (forced by -t option)\n");
    //printf("-A/--enter-audio  Process audio packets\n");

    printf("\n");
    printf("General Display Toggles\n");
    printf("-p/--pack         Toggle Pack packet start display\n");
    printf("-v/--video        Toggle video ES packet start display\n");
    printf("-a/--audio        Toggle audio ES packet start display\n");
    printf
        ("-o/--other        Toggle other stream display (only show video & audio)\n");
    printf("-y/--system       Toggle system header display\n");
    printf("-e/--errors       Toggle packet errors\n");
    printf("-i/--invert       Invert all display toggles\n");

    printf("\n");
    printf("Video Display Toggles\n");
    printf("-s/--slices       Toggle slice starts (default off)\n");
    printf("-q/--sequence     Toggle sequence header display\n");
    printf("-g/--GOPs         Toggle GOP header display\n");
    printf("-f/--frames       Toggle Picture header display\n");

    //printf("Audio Display Toggles\n");

    printf("\n");
    printf("Short Cuts\n");
    printf
        ("-F/--just-frames  Show only video frames (same as '-Vepvaoyq')\n");

    printf("\n");
    printf("File Offset Options\n");
    printf("-b/--begin=NUM    Start reading at offset NUM\n");
    printf("-n/--num=NUM      Stop after reading NUM bytes\n");
    printf
        ("-t/--scan-stream  Scan every byte of the stream instead of using PES offsets\n");
    printf
        ("                  (this can show erroneous 'start code emulation' codes)\n");
    printf("\n");
    printf("General Options\n");
    printf("-D/--dvd-format               Display DVD packet extensions\n");
    printf("-d/--debug                    Report debug info\n");
    printf
        ("-w/--write FILE,0xID[,0xSID]  Write PES with id ID (and subid SID) to FILE\n");
    printf("-h/-?/--help                  Display this help\n");

    exit(1);
}

/* utilities for dealing with timecodes */
struct time_code_t
{
    unsigned char drop;     // 0-1
    unsigned char hours;    // 0-23
    unsigned char minutes;  // 0-59
    unsigned char seconds;  // 0-59
    unsigned char pictures; // 0-59
};

/*
void time_code_to_string(struct time_code_t * time_code, char * buf, int len)
{
    if (!time_code) return;
    snprintf(buf,len,"%02d:%02d:%02d.%02d",
             time_code->hours,
             time_code->minutes,
             time_code->seconds,
             time_code->pictures);
}

void time_code_plus_pictures(struct time_code_t * time_code,
                             unsigned char fps,
                             unsigned char pictures)
{
    switch (fps)
    {
        case FPS_23_9:

#define FPS_23_9    1   // 0001 -- 24000/1001
#define FPS_24      2   // 0010 -- 24
#define FPS_25      3   // 0011 -- 25
#define FPS_29_9    4   // 0100 -- 30000/1001
#define FPS_30      5   // 0101 -- 30
#define FPS_50      6   // 0110 -- 50
#define FPS_59_9    7   // 0111 -- 60000/1001
#define FPS_60      8   // 1000 -- 60

    
}
*/

file_buf * fb=NULL;

typedef struct
{
    int id;                     // PES id
    int sid;                    // PES sub-id
    char *filename;             // copy of filename
    FILE *fp;                   // file handle to write it to
}
writer_t;

int max_pes_writers = 0;
writer_t *pes_writers = NULL;

FILE *writer_match(int id, int sid)
{
    int i;

    //fprintf(stderr,"Looking for 0x%02X:0x%02X\n",id,sid);

    for (i = 0; i < max_pes_writers; i++)
    {
        /*
           fprintf(stderr,"Trying %d: 0x%02X:0x%02X\n",i,
           pes_writers[i].id,
           pes_writers[i].sid);
         */
        if (pes_writers[i].id == id && pes_writers[i].sid == sid)
        {
            //fprintf(stderr,"  got it: %d\n",i);
            return pes_writers[i].fp;
        }
    }
    return NULL;
}

int writer_add(int id, int sid, char *filename)
{
    FILE *fp;
    int index;

    if (writer_match(id, sid))
        return -1;

    if (!(fp = fopen(filename, "w")))
    {
        perror(filename);
        return -1;
    }

    index = max_pes_writers++;
    if (!(pes_writers = (writer_t *) realloc(pes_writers,
                                             sizeof(writer_t) *
                                             max_pes_writers)))
    {
        perror("realloc");
        return -1;
    }

    pes_writers[index].id = id;
    pes_writers[index].sid = sid;
    pes_writers[index].fp = fp;
    pes_writers[index].filename = strdup(filename);

    //fprintf(stderr,"Storing 0x%02X:0x%02X to '%s'\n",id,sid,filename);

    return 0;
}

void writer_done()
{
    int i;

    for (i = 0; i < max_pes_writers; i++)
    {
        fclose(pes_writers[i].fp);
        free(pes_writers[i].filename);
    }
    free(pes_writers);
    pes_writers = NULL;
}

typedef enum
{
    PACK_NONE,
    PACK_SPECIAL,
    PACK_PES_SIMPLE,            // packet length == data length
    PACK_PES_COMPLEX,           // crazy headers need skipping
}
packet_type;

typedef enum
{
    SUBID_NONE,                 // no sub id (sid=0)
    SUBID_DATA,                 // first PES data byte
}
subid_type;


// FIXME: 
//   - add "valid scope" flags (eg some markers only valid in Video stream)
//   - add "struct size" field for auto-forwarding past each marker
typedef struct
{
    // the byte value match for the packet tags
    uint8_t code_match_lo;      // low end of the range of matches
    uint8_t code_match_hi;      // high end of the range of matches

    // what kind of PES is it?
    packet_type packet;

    // how do we find the stream sub id
    subid_type subid_method;

    // misc stuff... sorting types?
}
packet_tag_info;

packet_tag_info packet_tags[] = {
    {0x00, 0x00, PACK_SPECIAL, SUBID_NONE},     // pic start
    {0x01, 0xAF, PACK_SPECIAL, SUBID_NONE},     // video slices
    {0xB0, 0xB1, PACK_SPECIAL, SUBID_NONE},     // reserved
    {0xB2, 0xB5, PACK_SPECIAL, SUBID_NONE},     // user data, sequences
    {0xB6, 0xB6, PACK_SPECIAL, SUBID_NONE},     // reserved
    {0xB7, 0xB9, PACK_SPECIAL, SUBID_NONE},     // sequence, gop, end
    {0xBA, 0xBA, PACK_SPECIAL, SUBID_NONE},     // pack
    {0xBB, 0xBB, PACK_PES_SIMPLE, SUBID_NONE},  // system: same len as PES
    {0xBC, 0xBC, PACK_PES_SIMPLE, SUBID_NONE},  // PES: prog stream map
    {0xBD, 0xBD, PACK_PES_COMPLEX, SUBID_DATA}, // PES: priv 1
    {0xBE, 0xBF, PACK_PES_SIMPLE, SUBID_NONE},  // PES: padding, priv 2
    {0xC0, 0xDF, PACK_PES_COMPLEX, SUBID_NONE}, // PES: Audio
    {0xE0, 0xEF, PACK_PES_COMPLEX, SUBID_NONE}, // PES: Video
    {0xF0, 0xF2, PACK_PES_SIMPLE, SUBID_NONE},  // PES: ecm, emm, dsmcc
    {0xF3, 0xF7, PACK_PES_COMPLEX, SUBID_NONE}, // PES: iso 13522/h2221a-d
    {0xF8, 0xF8, PACK_PES_SIMPLE, SUBID_NONE},  // PES: h2221e
    {0xF9, 0xF9, PACK_PES_COMPLEX, SUBID_NONE}, // PES: ancillary
    {0xFA, 0xFE, PACK_PES_SIMPLE, SUBID_NONE},  // PES: reserved
    {0xFF, 0xFF, PACK_PES_SIMPLE, SUBID_NONE},  // PES: prog stream dir
    {0, 0, PACK_NONE, SUBID_NONE}       // end of list
};

int handle_options(int argc, char *argv[])
{
    int c;
    int option_index = 0;

    while (1)
    {
        c = getopt_long(argc, argv, short_options, long_options,
                        &option_index);
        if (c == -1)
            break;

        switch (c)
        {
            case '?':
            case 'h':
                usage(argv);
                break;

            case 'd':
                debug = !debug;
                break;

            case 'D':
                dvd_names = !dvd_names;
                break;

            case 'e':
                no_errors = !no_errors;
                break;

            case 's':
                no_slices = !no_slices;
                break;

            case 'a':
                no_audio = !no_audio;
                break;

            case 'v':
                no_video = !no_video;
                break;

            case 'p':
                no_pack = !no_pack;
                break;

            case 'q':
                no_sequence = !no_sequence;
                break;

            case 'o':
                no_other_streams = !no_other_streams;
                break;

            case 'y':
                no_system_headers = !no_system_headers;
                break;

            case 'g':
                no_GOP_headers = !no_GOP_headers;
                break;

            case 'f':
                no_picture_headers = !no_picture_headers;
                break;

            case 'F':
                no_pack = !no_pack;
                no_video = !no_video;
                no_audio = !no_audio;
                no_other_streams = !no_other_streams;
                no_system_headers = !no_system_headers;
                no_errors = !no_errors;

                no_sequence = !no_sequence;

                enter_video = !enter_video;
                break;

            case 'i':
                no_pack = !no_pack;
                no_audio = !no_audio;
                no_video = !no_video;
                no_other_streams = !no_other_streams;
                no_system_headers = !no_system_headers;

                no_slices = !no_slices;
                no_sequence = !no_sequence;
                no_GOP_headers = !no_GOP_headers;
                no_picture_headers = !no_picture_headers;
                break;

            case 'V':
                enter_video = !enter_video;
                break;

            case 't':
                walk_offsets = !walk_offsets;
                break;

            case 'n':
                num_bytes = atoll(optarg);
                break;

            case 'b':
                begin_at = atoll(optarg);
                break;

            case 'w':
                {
                    char *filename;
                    char *id_str;
                    char *sid_str;
                    int id;
                    int sid;

                    filename = strtok(optarg, ",");
                    id_str = strtok(NULL, ",");
                    sid_str = strtok(NULL, ",");

                    if (!filename || !id_str ||
                        sscanf(id_str, "%x", &id) != 1 ||
                        (sid_str && sscanf(sid_str, "%x", &sid) != 1))
                        usage(argv);
                    if (!sid_str)
                        sid = 0;

                    if (writer_add(id, sid, filename) < 0)
                        exit(1);
                    break;
                }

            default:
                printf("?? getopt returned character code 0x%x ??\n", c);
                break;
        }
    }


    return optind;
}

static char *length_format;

uint8_t packet_buffer[65536];   // max size of a double byte
uint8_t *packet_start;
uint16_t packet_size;

char *code_string(uint8_t code, off_t * position)
{
    static char answer[128];
    uint8_t bytes[32];
    int i;

    // length variables
    // HI LO
    unsigned int length;
    unsigned int header_len;
    static char length_str[64];

    // pack header variables
    // 
    //|   0   |   1   |   2   |   3   |   4   |   5   |   6   |
    // 76543210765432107654321076543210765432107654321076543210
    // xxHHHxMMMMMMMMMMMMMMMxLLLLLLLLLLLLLLLxEEEEEEEEExRRRRRRRR
    //|   7   |   8   |   9   |
    // 765432107654321076543210
    // RRRRRRRRRRRRRRxxrrrrrSSS
    unsigned long long system_clock_ref_base;   // "H"igh, "M"edium, "L"ow bits
    unsigned long system_clock_ref_ext; // "E"
    unsigned int program_mux_rate;      // "R"
    unsigned int pack_stuffing_length;  // "S"

    // sequence header variables
    //|horizontal |vertical   |asp|rat|bit rate         ||vbv_buff...
    //|   0   |   1   |   2   |   3   |   4   |   5   |   6   |
    // 76543210765432107654321076543210765432107654321076543210
    // ... |c
    //|   7   |   8   |   9   |
    // 765432107654321076543210
    int horizontal_size;
    int vertical_size;
    int aspect_ratio;
    int frame_rate;
    int bit_rate;
    int constrained;

    // picture header variables;
    //|temporal |ty|
    //|   0   |   1   |   2   |   3   |   4   |   5   |   6   |
    // 76543210765432107654321076543210765432107654321076543210
    int pic_type;
    int pic_time;

    // GOP header variables
    // |    0    |    1    |    2   |    3    |
    //  7 65432 107654 3 210765 432107 6 543210
    //  1 11111 111111 1 111111 111111 1 1
    //  d hour  min    m sec    pic    c b
    //  r              a               l roken
    //  o              r               osed
    //  p              k
    int drop;
    int hour;
    int min;
    int sec;
    int pictures;
    int closed;
    int broken;


    // initialize "unknown" packet length
    header_len = length = 0;
    length_str[0] = '\0';


    // find the packet type
    for (i = 0; packet_tags[i].packet != PACK_NONE; i++)
    {
        if (code >= packet_tags[i].code_match_lo &&
            code <= packet_tags[i].code_match_hi)
        {
            FILE *fp;
            int sid = 0;

            switch (packet_tags[i].packet)
            {
                case PACK_SPECIAL:
                    // actually, nothing special
                    break;
                case PACK_PES_SIMPLE:
                case PACK_PES_COMPLEX:
                    if (packet_tags[i].packet == PACK_PES_COMPLEX)
                    {
                        if (buffer_look_ahead(fb, bytes, 5) != FILE_BUF_OKAY)
                            return NULL;
                        header_len = 5 + bytes[4];

                        if (dvd_names &&
                            packet_tags[i].subid_method ==
                                SUBID_DATA &&
                            buffer_look_ahead(fb, bytes, header_len + 1) ==
                                FILE_BUF_OKAY)
                        {
                            sid = bytes[header_len];
                        }
                    }
                    else
                    {
                        if (buffer_look_ahead(fb, bytes, 2) != FILE_BUF_OKAY)
                            return NULL;
                    }

                    length = bytes[1] | (bytes[0] << 8);

                    // "2" comes from the "length" bytes
                    sprintf(length_str, length_format,
                            length, length == 1 ? "" : "s",
                            "marker", buffer_tell(fb) + length + 2);

                    // get position updated for next packet location
                    if (position && walk_offsets)
                    {
                        if (enter_video &&
                            (code & PES_TYPE_MASK_video) == PES_TYPE_video)
                        {
                            break;      // skip position update
                        }
                        *position = buffer_tell(fb) + length + 2;
                    }

                    // write out any possible matches
                    if ((fp = writer_match(code, sid))) {
                        if (buffer_look_ahead(fb,packet_buffer, length + 2) ==
                                FILE_BUF_OKAY) {

                            packet_start = packet_buffer;
                            if (header_len) {
                                // skip sub id byte
                                packet_start += header_len;
                                packet_size = length - header_len + 2;
                                if (dvd_names &&
                                    packet_tags[i].subid_method == SUBID_DATA) {
                                    packet_start++;
                                    packet_size--;
                                }
                            }
                            else {
                                // packet length bytes
                                packet_start += 2;
                                packet_size = length;
                            }

                            if (fwrite(packet_start, packet_size, 1, fp) < 1) {
                                perror("fwrite");
                            }
                            else {
                                //fprintf(stderr,"wrote %d\n",packet_size);
                            }
                        }
                    }

                    break;
                default:
                case PACK_NONE:
                    // FIXME: not possible!
                    break;
            }

            break;              // found a match
        }
    }



    switch (code)
    {
        case PES_TYPE_picture_start:
            if (no_picture_headers)
                return NULL;

            if (buffer_look_ahead(fb,bytes, 2) == FILE_BUF_OKAY)
            {
                pic_type = (bytes[1] & PICTURE_CODING_MASK) >> 3;
                pic_time = (bytes[0] << 2) | ((bytes[1] & 0xC0) >> 6);

                sprintf(answer, "  Picture (%s) Start (%u)",
                        pic_str[pic_type], pic_time);
                return answer;
            }
            else
                return "  Picture Start (incomplete flags)";

        case PES_TYPE_user_data_start:
            if (no_other_streams)
                return NULL;
            return " User Data Start";

        case PES_TYPE_sequence_header:
            if (no_sequence)
                return NULL;

            if (buffer_look_ahead(fb,bytes, 8) == FILE_BUF_OKAY)
            {
                horizontal_size = (bytes[0] << 4) | ((bytes[1] & 0xF0) >> 4);
                vertical_size = ((bytes[1] & 0x0F) << 8) | bytes[2];
                aspect_ratio = (bytes[3] & 0xF0) >> 4;
                frame_rate = (bytes[3] & 0x0F);
                bit_rate =
                    (bytes[4] << 10) | (bytes[5] << 2) |
                    ((bytes[6] & 0xC0) >> 6);
                constrained = (bytes[7] & 0x04) >> 2;

                sprintf(answer,
                        "  Sequence Header (%dx%d: %s, %sfps, %s%s)",
                        horizontal_size, vertical_size,
                        aspect_str[aspect_ratio],
                        frame_str[frame_rate],
                        speed_str(bit_rate * 400),
                        constrained ? " constrained" : "");
                return answer;
            }
            else
                return "  Sequence Header (incomplete flags)";

        case PES_TYPE_sequence_error:
            if (no_sequence)
                return NULL;
            return "  Sequence Error";

        case PES_TYPE_extension_start:
            if (no_sequence)
                return NULL;
            return "  Sequence Extension Start";

        case PES_TYPE_sequence_end:
            if (no_sequence)
                return NULL;
            return "  Sequence End";

        case PES_TYPE_group_start:
            if (no_GOP_headers)
                return NULL;

            if (buffer_look_ahead(fb,bytes, 4) == FILE_BUF_OKAY)
            {
    // GOP header variables
    // |    0    |    1    |    2   |    3    |
    //  7 65432 107654 3 210765 432107 6 543210
    //  1 11111 111111 1 111111 111111 1 1
    //  d hour  min    m sec    pic    c b
    //  r              a               l roken
    //  o              r               osed
    //  p              k
                drop = ((bytes[0] & 0x80) > 0);
                hour = ((bytes[0] & 0x7C) >> 2);
                min = ((bytes[0] & 0x3) << 4) | ((bytes[1] & 0xF0) >> 4);
                sec = ((bytes[1] & 0x7) << 3) | ((bytes[2] & 0xE0) >> 5);
                pictures =
                    ((bytes[2] & 0x1F) << 1) | ((bytes[3] & 0x80) >> 7);
                closed = ((bytes[3] & 0x40) > 0);
                broken = ((bytes[3] & 0x20) > 0);

                sprintf(answer,
                        "  GOP Start (%02d:%02d:%02d.%02d%s%s%s)",
                        hour, min, sec, pictures,
                        drop ? " drop" : "",
                        closed ? " closed" : " open",
                        broken ? " broken" : "");
                return answer;
            }
            else
            {
                return "  GOP Start (incomplete flags)";
            }

        case PES_TYPE_program_end:
            if (no_other_streams)
                return NULL;
            return "Program End";

        case PES_TYPE_pack_start:
            if (no_pack)
                return NULL;

            if (buffer_look_ahead(fb,bytes, 10) == FILE_BUF_OKAY)
            {

                //|   0   |   1   |   2   |   3   |   4   |   5   |   6   |
                // 76543210765432107654321076543210765432107654321076543210
                // xxHHHxMMMMMMMMMMMMMMMxLLLLLLLLLLLLLLLxEEEEEEEEExRRRRRRRR
                //   333 222222222211111 111110000000000 000000000 22111111// bit
                //   210 987654321098765 432109876543210 876543210 10987654//     number
                //     -  -       -    -  -       -    -  -      -        -
                //     2  2       2    1  1       0    -  0      -        1// left
                //     7  8       0    2  3       5    3  7      1        4//      shift
                //
                //|   7   |   8   |   9   |
                // 765432107654321076543210
                // RRRRRRRRRRRRRRxxrrrrrSSS
                // 11110000000000  00000000   // bit
                // 32109876543210  43210210   //     number
                //        -     -      -  -
                //        0     -      -  0   // left
                //        6     2      3  0   //      shift

                system_clock_ref_base = (((bytes[0] & 0x38) << 27) |
                                         ((bytes[0] & 0x03) << 28) |
                                         ((bytes[1] & 0xFF) << 20) |
                                         ((bytes[2] & 0xF8) << 12) |
                                         ((bytes[2] & 0x03) << 13) |
                                         ((bytes[3] & 0xFF) << 5) |
                                         ((bytes[4] & 0xF8) >> 3));
                system_clock_ref_ext = (((bytes[4] & 0x03) << 7) |
                                        ((bytes[5] & 0xFE) >> 1));
                program_mux_rate = (((bytes[6] & 0xFF) << 14) |
                                    ((bytes[7] & 0xFF) << 6) |
                                    ((bytes[8] & 0xFC) >> 2));
                pack_stuffing_length = (bytes[9] & 0x07);

                sprintf(answer, "Pack Start (Mux Rate: %u Stuffing: %u)",
// FIXME: too many damn bits
//                                system_clock_ref_base,
//                                system_clock_ref_ext,
                        program_mux_rate, pack_stuffing_length);
                return answer;
            }
            else
            {
                return "Pack Start (incomplete flags)";
            }

        case PES_TYPE_system_header:
            if (no_system_headers)
                return NULL;

            sprintf(answer, " System Header%s", length_str);
            return answer;
        case PES_TYPE_program_stream_map:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " Program Stream Map%s", length_str);
            return answer;

        case PES_TYPE_private_stream_1:
            if (dvd_names)
            {
                char *audio_type;
                int raw_id, id;

                if (no_audio)
                    return NULL;
                // already done in the header checks above
                //buffer_look_ahead(fb,bytes,header_len+1);

                audio_type = "unknown sub stream id";
                raw_id = id = bytes[header_len];

                if ((raw_id & DVD_AUDIO_TYPE_MASK_sub) == DVD_AUDIO_TYPE_sub)
                {
                    audio_type = "SPU";
                    id = (raw_id & (DVD_AUDIO_TYPE_sub - 1));
                }
                else if ((raw_id & DVD_AUDIO_TYPE_MASK_ac3) ==
                         DVD_AUDIO_TYPE_ac3)
                {
                    audio_type = "AC3";
                    id = (raw_id & (DVD_AUDIO_TYPE_ac3 - 1));
                }
                else if ((raw_id & DVD_AUDIO_TYPE_MASK_pcm) ==
                         DVD_AUDIO_TYPE_pcm)
                {
                    audio_type = "PCM";
                    id = (raw_id & (DVD_AUDIO_TYPE_pcm - 1));
                }

                sprintf(answer, " DVD Audio 0x%02X (%s %d)%s",
                        raw_id, audio_type, id, length_str);
            }
            else
            {
                if (no_other_streams)
                    return NULL;
                sprintf(answer, " Private Stream 1%s", length_str);
            }
            return answer;

        case PES_TYPE_padding_stream:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " Padding Stream%s", length_str);
            return answer;
        case PES_TYPE_private_stream_2:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " %s%s",
                    dvd_names ? "DVD Navigation" : "Private Stream 2",
                    length_str);
            return answer;
        case PES_TYPE_ECM_stream:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " ECM Stream%s", length_str);
            return answer;
        case PES_TYPE_EMM_stream:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " EMM Stream%s", length_str);
            return answer;
        case PES_TYPE_DSMCC_stream:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " DSMCC Stream%s", length_str);
            return answer;
        case PES_TYPE_ISO_13522:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " ISO13522 Stream%s", length_str);
            return answer;
        case PES_TYPE_H2221A:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " H222.1A%s", length_str);
            return answer;
        case PES_TYPE_H2221B:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " H222.1B%s", length_str);
            return answer;
        case PES_TYPE_H2221C:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " H222.1C%s", length_str);
            return answer;
        case PES_TYPE_H2221D:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " H222.1D%s", length_str);
            return answer;
        case PES_TYPE_H2221E:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " H222.1E Stream%s", length_str);
            return answer;
        case PES_TYPE_ancillary_stream:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " Ancillary Stream%s", length_str);
            return answer;
        case PES_TYPE_program_stream_directory:
            if (no_other_streams)
                return NULL;
            sprintf(answer, " Program Stream Directory%s", length_str);
            return answer;
        default:
            if (code >= 0x01 && code <= 0xAF)
            {
                if (no_slices)
                    return NULL;
                sprintf(answer, "  Slice Start%s", length_str);
                return answer;
            }
            if ((code & PES_TYPE_MASK_video) == PES_TYPE_video)
            {
                if (no_video)
                    return NULL;

                sprintf(answer, " Video Stream 0x%02X%s",
                        PES_TYPE_video -
                        (code & PES_TYPE_MASK_video), length_str);
                return answer;
            }
            if ((code & PES_TYPE_MASK_audio) == PES_TYPE_audio)
            {
                if (no_audio)
                    return NULL;

                sprintf(answer, " Audio Stream 0x%02X%s",
                        PES_TYPE_audio -
                        (code & PES_TYPE_MASK_audio), length_str);
                return answer;
            }
            if ((code >= 0xFA && code <= 0xFE) ||
                (code >= 0xB0 && code <= 0xB1) || (code == 0xB6))
            {
                if (no_errors)
                    return NULL;

                sprintf(answer, "Reserved%s", length_str);
                return answer;
            }

            if (no_errors)
                return NULL;

            sprintf(answer, "Unknown%s", length_str);
            return answer;
    }
    return "IMPOSSIBLE ERROR: no match?!";
}


int main(int argc, char *argv[])
{
    int i, optind;
    uint32_t marker;
    uint8_t byte;
    char *report_format;

    optind = handle_options(argc, argv);

    if (optind >= argc)
        usage(argv);

    report_format = "%10" OFF_T_FORMAT ": 0x%02X: %s\n";
    length_format = ": %u byte%s (next %s @ %" OFF_T_FORMAT ")";

    if (debug)
    {
        int i;

        printf("Debug: on (off_t format is %%" OFF_T_FORMAT ")\n");
        printf("Starting from offset %" OFF_T_FORMAT "\n", begin_at);
        printf("Stopping at offset   %" OFF_T_FORMAT "\n",
               begin_at + num_bytes);
        for (i = 0; i < max_pes_writers; i++)
        {
            printf("Writing PES id 0x%02X:0x%02X to '%s'\n",
                   pes_writers[i].id,
                   pes_writers[i].sid, pes_writers[i].filename);
        }
        if (walk_offsets)
        {
            printf
                ("Examining packet structures for offset to next packet marker.\n");
            printf("%sEntering video packets to find sub packets.\n",
                   enter_video ? "" : "NOT ");
        }
        else
        {
            printf
                ("Blindly scanning every byte for possible 0x00 0x00 0x01 0x?? markers.\n");
            printf("(Entering video packets to find sub packets.)\n");

        }
        if (dvd_names)
        {
            printf("Using DVD packet names.\n");
        }
        else
        {
            printf("Using MPEG2-PS packet names.\n");
        }
        printf("%sDisplaying unexpected PES IDs.\n", no_errors ? "NOT " : "");
        printf("%sDisplaying Pack starts.\n", no_pack ? "NOT " : "");
        printf("%sDisplaying System Headers.\n",
               no_system_headers ? "NOT " : "");
        printf("%sDisplaying Video starts.\n", no_video ? "NOT " : "");
        printf("%sDisplaying Audio starts%s.\n",
               no_audio ? "NOT " : "",
               dvd_names ? " (both MPEG2-PS Audio and Priv 1)" : "");
        printf("%sDisplaying Other starts.\n",
               no_other_streams ? "NOT " : "");
        if (enter_video || !walk_offsets)
        {
            printf("%sDisplaying Video Slice starts.\n",
                   no_slices ? "NOT " : "");
            printf("%sDisplaying Video Sequence starts.\n",
                   no_sequence ? "NOT " : "");
            printf("%sDisplaying Video GOP Headers.\n",
                   no_GOP_headers ? "NOT " : "");
            printf("%sDisplaying Video Picture Headers.\n",
                   no_picture_headers ? "NOT " : "");

        }
        printf("\n");
    }

    for (i = optind; i < argc; i++)
    {
        int running = 1;

        if (!(fb=buffer_start(fb,argv[i],DEFAULT_FILE_BUFFER_SIZE))) {
            perror(argv[i]);
            continue;
        }

        buffer_seek(fb,begin_at);


        /*
           for (running=0;running<8;running++)
           {
           off_t loc;
           uint8_t byte;

           loc=buffer_tell();
           buffer_get_byte(&byte);
           printf("%" OFF_T_FORMAT ": %d\n",loc,byte);
           }
           buffer_seek(32);
           for (running=0;running<8;running++)
           {
           off_t loc;
           uint8_t byte;

           loc=buffer_tell();
           buffer_get_byte(&byte);
           printf("%" OFF_T_FORMAT ": %d\n",loc,byte);
           }
           exit(0);
         */


        marker = 0xFFFFFFFF;
        while (running)
        {
            off_t newpos;

            //fprintf(stderr,"%08X\n",marker);
            if ((marker & 0xFFFFFF00) == 0x100)
            {
                char *str;

                newpos = 0;
                if ((str = code_string(byte, &newpos)))
                {
                    printf(report_format, buffer_tell(fb) - 4, byte, str);
                }
                // we skipped to a new location?
                if (newpos != 0)
                {
                    //fprintf(stdout,"skipping to %" OFF_T_FORMAT "\n",newpos);
                    marker = 0xFFFFFFFF;
                    buffer_seek(fb,newpos);
                }
            }
            marker <<= 8;
            if (buffer_get_byte(fb,&byte) < 0)
            {
                printf(report_format, buffer_tell(fb), 0x00, "End of File");
                running = 0;
            }
            else
                marker |= byte;

            if (num_bytes && buffer_tell(fb) - begin_at > num_bytes)
            {
                printf(report_format, buffer_tell(fb), 0x00, "End of Report");
                running = 0;
            }
        }
    }

    writer_done();

    return 0;
}

/* vi:set ai ts=4 sw=4 expandtab: */
