/*				mpegUtil.c

MpegUtil is an analysis and edit program for MPEG-1 video streams.
    
Copyright (c) Phillip Lougher, March 1995.
    
Permission to use and distribute this software and its documentation is
hereby granted, subject to the provision that all files are distributed
unaltered, and that no profit is made from its distribution.

This software is provided "as is", without any warranty either express or
implied.  In no event shall the author (Phillip Lougher) be liable for
any damages arising out of the use of this software.  The user of this
software assumes all risk as to its quality and accuracy.
*/

#include <stdio.h>
#include "mpegUtil.h"

/* Global variables */
pictureInfo *pictureTable[MAX_BLOCKS];
long int *displayTable[MAX_BLOCKS];

long int cur_bit, bytesToRead, i, output_bit, bytesOutput, fullStatistics,
    output, startFrame, endFrame, noP, noB, avI, avB, avP, avGop,
    totalI, totalP, totalB, totalPictures, totalGops, totalClosed, totalOpen,
    notOutputSequenceHeader, notOutputGopHeader, actualStart, actualEnd,
    gop_array_total, frames;
char *outputFilename;
unsigned char *cur_byte, buff[BUFF_SIZE], *output_byte,
    output_buffer[BUFF_SIZE + 1];
FILE *input_file, *output_file;
gop_average gop_array[GOP_ARRAY_LENGTH];

/* Variables for storing SEQUENCE HEADER data */
unsigned long int width, height, pel_aspect_ratio, marker_bit, vbv_buffer_size,
    picture_rate, constrained, load_intra, load_non_intra, ext_data, 
    bit_rate, user_data, sequence_size;
unsigned char intra_quantizer_matrix[64], non_intra_quantizer_matrix[64];
unsigned char ext[1024], user[1024];

/* Variables for storing GROUP OF PICTURES data */
unsigned long int time_code, closed_gop, broken_link, gop_ext_data, gop_user_data,
    gop_size, pictures, gopPictureStart, gopDisplayEnd, lastPictureStored,
    Icount,  temporal_offset;
unsigned char gop_ext[1024], gop_user[1024];

/* Variables for storing PICTURE data */
long int picture_size, mallocedSize, dataLeft;
pictureInfo *p;
unsigned char *ptr;
void process_sequence();
void update_gop_average();
long int valid_index();


void error(s)
char *s;
{
fprintf(stderr, "\n%s\n", s);
exit(1);
}


#define get_next_data()\
if(!bytesToRead) {\
    if((bytesToRead = fread(buff, 1, BUFF_SIZE, input_file)) == 0)\
        error("Unexpected end of file.  MPEG file is corrupted");\
    else\
        {\
        cur_byte = buff;\
        cur_bit = 7;\
        }\
    }\


unsigned long int get_bit()
{
unsigned long int bit;

get_next_data();
bit = (*cur_byte >> cur_bit) & 1;
if(cur_bit == 0)
    {
    cur_byte++;
    bytesToRead--;
    cur_bit = 7;
    }
else
    cur_bit--;
return bit;
}


unsigned long int get_byte()
{
unsigned long int byte;

get_next_data();
if(cur_bit != 7)
    error("Non byte aligned in get_byte.  MPEG file is corrupted");
byte = *cur_byte++;
bytesToRead--;
return byte;
}


unsigned long int get_bits(bits_left)
long int bits_left;
{
unsigned long int byte, result;

result = 0;
do {
    if(BYTEALIGNED && (bits_left >= 8))
        {
        bits_left -= 8;
        result |= get_byte() << bits_left;
        }
    else
        {
        byte = *cur_byte;
        byte &= (unsigned int) 0xffffffff >> (31 - cur_bit);
        if(cur_bit >= bits_left)
            {
            result |= byte >> (cur_bit - bits_left + 1);
            cur_bit -= bits_left;
            bits_left = 0;
            }
        else
            {
            result |= byte << (bits_left - cur_bit - 1);
            bits_left -= cur_bit + 1;
            cur_bit = 7;
            cur_byte++;
            bytesToRead--;
            get_next_data();
            }
        }
    } while(bits_left);
return result;
}


unsigned long int next_bits(bit_count, string)
long int bit_count;
unsigned long int string;
{
unsigned char *ptr;

if(!BYTEALIGNED)
    error("Comparing non byte aligned data.  MPEG file is corrupted");

/* Check that we have enough data in buffer to do non destructive read */
if((bit_count >> 3) > bytesToRead)
    {
    bcopy(cur_byte, buff, bytesToRead);
    bytesToRead += fread(buff+bytesToRead, 1, BUFF_SIZE-bytesToRead,
        input_file);
    cur_byte = buff;
    }

ptr = cur_byte;
do {
   if(*ptr++ != ((string >> (bit_count -= 8)) & 255))
       return 0;
   } while(bit_count);
return 1;
}


long int next_start_code()
{
/* Skip any leading zero bit stuffing */
while(!BYTEALIGNED)
    if(get_bit())
        error("Not an MPEG file.  Cannot find start code");

/* Check if we are at the start code.  If not there is zero byte stuffing */
while(!next_bits(24, 0x000001))
    if(get_byte())
        error("Not an MPEG file.  Cannot find start code");
}


#ifdef debug
#define PRINTLINE printf("\nReallocating data for picture %d\n", totalPictures)
#else
#define PRINTLINE 
#endif

#define checkForBufferSpace()\
if(!(dataLeft--)) {\
    PRINTLINE;\
    if((p->data = (extraData *) realloc((void *) p->data, mallocedSize +\
    INITIAL_DATA)) == NULL)\
        error("Out of memory in Picture data realloc");\
    else\
        {\
        ptr = ((unsigned char *) p->data) + mallocedSize;\
        mallocedSize += INITIAL_DATA;\
        dataLeft = INITIAL_DATA - 1;\
        }\
    }\
        
        
long int find_next_start_code()
{
long int bytes;

for(bytes = 0; !next_bits(24, 0x000001); bytes++)
    if(output)
        {
        checkForBufferSpace();
        *ptr++ = get_bits(8);
        }
    else
        get_bits(8);
return bytes;
}


void print_picture(temporal_reference)
long int temporal_reference;
{
printf("\nPicture %d read\n", totalPictures);
printf("Temporal reference %d\n", temporal_reference);
printf("Picture size %d\n", picture_size);
}


long int get_picture(gop, closed_gop, broken_link)
long int gop, closed_gop, broken_link;
{
/* This procedure reads a picture.  If the program is writing to a file,
then the picture is read into memory. */

long int temporal_reference, type, fragment;

if(pictureTable[totalPictures / MAX_PICTURES] == NULL) /* new block */
    if((pictureTable[totalPictures / MAX_PICTURES] = (pictureInfo *)
    malloc(sizeof(pictureInfo) * MAX_PICTURES)) == NULL)
        error("Out of memory in pictureTable malloc");

p = &pictureTable[totalPictures / MAX_PICTURES][totalPictures % MAX_PICTURES];
                
if(output)
    {    
#ifdef debug
    printf("\nAllocating data for picture %d\n", totalPictures);
#endif
    if((p->data = (extraData *) malloc(sizeof(extraData))) ==  NULL)
        error("Out of memory in Picture data malloc");
    mallocedSize = sizeof(extraData);
    dataLeft = INITIAL_DATA;
    ptr = p->data->bytes;
    }
else
    pictureTable[totalPictures / MAX_PICTURES][totalPictures %
    MAX_PICTURES].data = NULL;

/* Skip picture start code */
get_bits(32);
temporal_reference = get_bits(10);
type = get_bits(3);
fragment = get_bits(3);
picture_size = 6 + find_next_start_code();    
while(!(next_bits(32, GROUP_START_CODE) || next_bits(32,
    SEQUENCE_HEADER_CODE) || next_bits(32, SEQUENCE_END_CODE)
    || next_bits(32, PICTURE_START_CODE)))
    {
    /* Skip start code */
    for(i = 0; i < 4; i++)
        if(output)
            {
            checkForBufferSpace();
            *ptr++ = get_byte();
            }
        else
            get_byte();
    picture_size += 4 + find_next_start_code();
    }
switch(type)
    {
    case 1: putchar('I'); totalI++; avI += picture_size; break;
    case 2: putchar('P'); totalP++; avP += picture_size; break;
    case 3: putchar('B'); totalB++; avB += picture_size; break;
    case 4: putchar('D'); break;
    default: printf("*Reserved or forbidden*");
    }
fflush(stdout);
if(fullStatistics)
    print_picture(temporal_reference);

/* Fill in the picture table with the picture's values */
p->type = type;
p->gop = gop % 256;
p->gop_status = closed_gop | (broken_link << 1);
p->deleted = FALSE;
if(output)
    {
    p->data->temporal_reference = temporal_reference;
    p->data->fragment = fragment;
    p->data->mallocedSize = mallocedSize;
    p->data->picture_size = picture_size - 6;
    }

/* Compute the display order of this picture, and remember it for later */
temporal_reference +=  gopPictureStart;
if(displayTable[temporal_reference / MAX_PICTURES] == NULL)
    {
    if((displayTable[temporal_reference / MAX_PICTURES] = (long int *)
    malloc(sizeof(int) * MAX_PICTURES)) == NULL)
        error("Out of memory in displayTable malloc");
    /* Initialise each entry */
    for(i = 0; i < MAX_PICTURES; i ++)
        displayTable[temporal_reference / MAX_PICTURES][i] = -1;
    }
displayTable[temporal_reference / MAX_PICTURES][temporal_reference %
    MAX_PICTURES] = totalPictures;
if(gopDisplayEnd < temporal_reference)
    gopDisplayEnd = temporal_reference;
totalPictures ++;
return type;
}


void get_group_of_pictures()
{
/* This procedure reads a Gop picture by picture.  If the program is writing
to a file, then pictures are buffered pending picture processing (i.e.
removal of P and B frames).  Pictures are buffered until 3 I frames
have been read - this is to allow each picture's temporal reference count
to be properly recomputed on B or P frame removal */

long int type;

get_bits(32);
time_code = get_bits(25);
closed_gop = get_bit();
broken_link = get_bit();
next_start_code();
if(next_bits(32, EXTENSION_START_CODE))
    {
    get_bits(32);
    for(gop_ext_data = 0; !next_bits(24, 0x000001); gop_ext_data++)
        gop_ext[gop_ext_data] = get_byte();
    gop_size = 4 + gop_ext_data;
    next_start_code();
    }
else
    gop_size = gop_ext_data = 0;
if(next_bits(32, USER_DATA_START_CODE))
    {
    get_bits(32);
    for(gop_user_data = 0; !next_bits(24, 0x000001); gop_user_data++)
        gop_user[gop_user_data] = get_byte();
    gop_size += 4 + gop_user_data;
    next_start_code();
    }
else
    gop_user_data = 0;
gop_size += 8;

putchar('*');
fflush(stdout);

notOutputGopHeader = TRUE;
/* Setup up status variables.  These keep track of various aspects to
do with the current group of frames being parsed */
gopPictureStart = gopDisplayEnd = lastPictureStored = totalPictures;
temporal_offset = Icount = 0;
for(pictures = 0; next_bits(32, PICTURE_START_CODE); pictures++)
    {
    type = get_picture(totalGops, closed_gop, broken_link);
    gop_size += picture_size;
    if(type == I_FRAME)
        Icount ++;
    if(Icount == 3)
        {
        process_sequence(FALSE);
        lastPictureStored = totalPictures - 1;
        Icount = 1;
        }
    }
process_sequence(TRUE);
totalGops ++;
if(closed_gop)
    totalClosed ++;
else
    totalOpen ++;
avGop += gop_size;
update_gop_average(pictures);
}


void print_gop()
{
printf("\nGop %d read\n", totalGops);
printf("Time code %x\n", time_code);
printf("closed_gop %s\n", closed_gop ? "true" : "false");
printf("broken_link %s\n", broken_link ? "true" : "false");
if(gop_ext_data)
    printf("Extension data exists\n");
if(gop_user_data)
    printf("User data exists\n");
printf("Number of pictures %d\n", pictures);
printf("Size of gop %d\n", gop_size);
}


void get_sequence_header()
{
if(!next_bits(32, SEQUENCE_HEADER_CODE))
    error("Not an MPEG file.  Cannot find SEQUENCE HEADER");

get_bits(32);
width = get_bits(12);
height = get_bits(12);
pel_aspect_ratio = get_bits(4);
picture_rate = get_bits(4);
bit_rate = get_bits(18);
marker_bit = get_bit();
if(!marker_bit)
    error("Marker bit in SEQUENCE HEADER not set.  MPEG file is corrupted");
vbv_buffer_size = get_bits(10);
constrained = get_bit();
load_intra = get_bit();
if(load_intra)
    for(i = 0; i < 64; i++)
        intra_quantizer_matrix[i] = get_bits(8);
load_non_intra = get_bit();
if(load_non_intra)
    for(i = 0; i < 64; i++)
        non_intra_quantizer_matrix[i] = get_bits(8);
next_start_code();
if(next_bits(32, EXTENSION_START_CODE))
    {
    get_bits(32);
    for(ext_data = 0; !next_bits(24, 0x000001); ext_data++)
        ext[ext_data] = get_byte();
    next_start_code();
    }
else
    ext_data = 0;
next_start_code();
if(next_bits(32, USER_DATA_START_CODE))
    {
    get_bits(32);
    for(user_data = 0; !next_bits(24, 0x000001); user_data++)
        user[user_data] = get_byte();
    next_start_code();
    }
else
    user_data = 0;
sequence_size = 12 + (64 * load_intra) + (64 * load_non_intra);
if(ext_data)
    sequence_size += 4 + ext_data;
if(user_data)
    sequence_size += 4 + user_data;
notOutputSequenceHeader = TRUE;
}


void print_sequence_header()
{
printf("Got MPEG sequence header\n");
printf("Sequence header size %d\n", sequence_size);
printf("Frame width %d\n", width);
printf("Frame height %d\n", height);
printf("Pel aspect ratio (height to width) %s\n",
    aspectRatioMeaning[(pel_aspect_ratio > 16) ? 0 : pel_aspect_ratio]);
printf("Picture rate %s\n", pictureRateMeaning[(picture_rate > 8) ?
    9 : picture_rate]);
printf("Bit rate ");
if(bit_rate == 0x3ffff)
    printf("variable\n");
else
    printf("%d bits/s (%5.2f Kbytes/s)\n", bit_rate * 400,
        (bit_rate * 400.0) / 8192);
printf("Vbv buffer size %d\n", vbv_buffer_size);
if(load_intra)
    printf("Intra quantizer matrix exists\n");
else
    printf("Uses default intra quantizer matrix\n");
if(load_non_intra)
    printf("Non intra quantizer matrix exists\n");
else
    printf("Uses default non intra quantizer matrix\n");
if(ext_data)
    printf("Extension data exists\n");
if(user_data)
    printf("User data exists\n");
}


void put_next_data()
{
if(bytesOutput != BUFF_SIZE)
    return;

if(fwrite(output_buffer, 1, bytesOutput, output_file) < bytesOutput)
    {
    perror("Cannot output to output file\n");
    exit(1);
    }

output_byte = output_buffer;
output_bit = 7;
bytesOutput = 0;
}


void put_bit(bit)
long int bit;
{
*output_byte |= bit << output_bit;
if(output_bit == 0)
    {
    output_byte++;
    *output_byte = 0;
    output_bit = 7;
    bytesOutput++;
    put_next_data();
    }
else
    output_bit--;
}


void put_byte(byte)
unsigned long int byte;
{
if(output_bit != 7)
    error("Not byte aligned in output");
*output_byte++ = byte;
*output_byte = 0;
bytesOutput++;
put_next_data();
}


void put_bits(bits_left, bits)
long int bits_left;
unsigned long int bits;
{
long int byte;

do  {
    if((output_bit == 7) && (bits_left >= 8))
        {
        bits_left -= 8;
        put_byte((bits >> bits_left) & 255);
        }
    else
        {
        byte = bits & ((unsigned int) 0xffffffff >> (32 - bits_left));
        if(output_bit >= bits_left)
            {
            *output_byte |= byte << (output_bit - bits_left + 1);
            output_bit -= bits_left;
            bits_left = 0;
            }
        else
            {
            *output_byte++ |= byte >> (bits_left - 1 - output_bit);
            *output_byte = 0;
            bits_left -= output_bit + 1;
            output_bit = 7;
            bytesOutput++;
            put_next_data();
            }
        }
    } while(bits_left);
}


void put_filler()
{
/*  Move to the next byte boundary, filling with 0 bits */
while(output_bit != 7)
    put_bit(0);
}

        
void output_sequence_header()
{
/* Output sequence header */
put_bits(32, SEQUENCE_HEADER_CODE);
put_bits(12, width);
put_bits(12, height);
put_bits(4, pel_aspect_ratio);
put_bits(4, picture_rate);
put_bits(18, bit_rate);
/* Marker bit */
put_bit(1);
put_bits(10, vbv_buffer_size);
put_bit(constrained);
put_bit(load_intra);
if(load_intra)
    for(i = 0; i < 64; i++)
        put_bits(8, intra_quantizer_matrix[i]);
put_bit(load_non_intra);
if(load_non_intra)
    for(i = 0; i < 64; i++)
        put_bits(8, non_intra_quantizer_matrix[i]);
/* Extension data */
if(ext_data)
    {
    put_filler();
    put_bits(32, EXTENSION_START_CODE);
    for(i = 0; i < ext_data; i++)
        put_byte(ext[i]);
    }
/* User data */
if(user_data)
    {
    put_filler();
    put_bits(32, USER_DATA_START_CODE);
    for(i = 0; i < user_data; i++)
        put_byte(user[i]);
    }
notOutputSequenceHeader = FALSE;
}


/* This procedure is used to compute the modal average number of pictures
in a gop */
void update_gop_average(pictures)
long int pictures;
{
long int i;

for(i = 0; (i < gop_array_total) && (gop_array[i].pictures != pictures); i++);
if(i == gop_array_total)
    {
    if(gop_array_total < GOP_ARRAY_LENGTH)
        {
        gop_array[i].pictures = pictures;
        gop_array[i].occurrences = 1;
        gop_array_total++;
        }
    }
else
    gop_array[i].occurrences++;
}


long int display_modal_gop_average()
{
long int i, count;

for(i = count = 0; i < gop_array_total; i++)
    if(gop_array[i].occurrences > count)
        count = gop_array[i].occurrences;
for(i = 0; i < gop_array_total; i ++)
    if(gop_array[i].occurrences == count)
        printf(" %d %s,", gop_array[i].pictures, (gop_array[i].pictures
        == 1) ? "picture" : "pictures");
if(count == 1)
    printf(" occurs once\n");
else
    printf(" occurs %d times\n", count);               
}

    
    
#define checkIfLineFull()\
if(charCount > 72)\
    {\
    printf("%s\n%s\n\n", line1, line2);\
    p1 = line1; p2 = line2; charCount = 0;\
    }


void displayStats()
{
long int charCount, i, index, curGop, uncom_size;
char line1[80], line2[80], *p1, *p2;
pictureInfo *ptr;

printf("\nMPEG file information summary\n");
printf("-----------------------------\n\n");
printf("Frame sequence (decode order)\n");
curGop = -1;
if(actualStart != -1)
    {
    for(p1 = line1, p2 = line2, charCount = 0, i = actualStart; i <=
    actualEnd; i++)
        {
        ptr = &pictureTable[i / MAX_PICTURES][i % MAX_PICTURES];
        checkIfLineFull();
        if(ptr->gop != curGop)
            {
            if(ptr->gop_status & CLOSED_GOP)
                strcpy(p2, strcpy(p1, " ||"));
            else if(ptr->gop_status & BROKEN_LINK)
                strcpy(p2, strcpy(p1, " |+"));
            else
                strcpy(p2, strcpy(p1, " | "));
            p1 +=3; p2 += 3; charCount +=3;
            curGop = ptr->gop;
            }
        checkIfLineFull();
        sprintf(p1, " %3d", i % 1000);
        switch(ptr->type)
            {
            case 1: strcpy(p2, "   I"); break;
            case 2: strcpy(p2, "   P"); break;
            case 3: strcpy(p2, "   B"); break;
            case 4: strcpy(p2, "   D"); break;
            default: strcpy(p2, "   -"); break;
            }
        p1 += 4; p2 +=4; charCount += 4;
        }
    printf("%s\n%s\n\n", line1, line2);
    printf("Frame sequence (display order)\n");
    curGop = -1;
    for(p1 = line1, p2 = line2, charCount = 0, i = actualStart; i <=
    actualEnd; i++)
        {
        if(!valid_index(i, &index))
            continue;
        ptr = &pictureTable[index / MAX_PICTURES][index % MAX_PICTURES];
        checkIfLineFull();
        if(ptr->gop != curGop)
            {
            if(ptr->gop_status & CLOSED_GOP)
                strcpy(p2, strcpy(p1, " ||"));
            else if(ptr->gop_status & BROKEN_LINK)
                strcpy(p2, strcpy(p1, " |+"));
            else
                strcpy(p2, strcpy(p1, " | "));
            p1 +=3; p2 += 3; charCount +=3;
            curGop = ptr->gop;
            }
        checkIfLineFull();
        sprintf(p1, " %3d", index % 1000);
        switch(ptr->type)
            {
            case 1: strcpy(p2, "   I"); break;
            case 2: strcpy(p2, "   P"); break;
            case 3: strcpy(p2, "   B"); break;
            case 4: strcpy(p2, "   D"); break;
            default: strcpy(p2, "   -"); break;
            }
        p1 += 4; p2 +=4; charCount += 4;
        }
    printf("%s\n%s\n\n", line1, line2);
    }

/* Print information about MPEG file.  These relate to the last
SEQUENCE HEADER read, however, it is unlikely that these will change */
printf("Frame size: width %d, height %d\n", width, height);
printf("Pixel aspect ratio (height to width) %s\n",
    aspectRatioMeaning[(pel_aspect_ratio > 16) ? 0 : pel_aspect_ratio]);
printf("Picture rate: %s\n",
    pictureRateMeaning[(picture_rate > 8) ? 9 : picture_rate]);
printf("Bit rate: ");
if(bit_rate == 0x3ffff)
    printf("variable\n");
else
    printf("%d bits/s (%5.2f Kbytes/s)\n", bit_rate * 400,
        (bit_rate * 400.0) / 8192);
printf("\nTotal number of pictures in sequence %d\n", totalPictures);
printf("    Made up of %d I, %d P and %d B frames\n", totalI, totalP, totalB);
printf("Total number of Groups of Pictures (random access points) %d\n",
    totalGops);
printf("    Made up of %d closed, and %d open GOPs\n", totalClosed, totalOpen);
printf("\nUncompressed picture size %d bytes\n", uncom_size = width * height *
    3);
printf("Average picture size %.2f bytes, %.2f%% of uncompressed picture\n",
    (avI + avP + avB) / (float) totalPictures,((avI + avP + avB) / (float)
    totalPictures) / uncom_size * 100);
printf("Average size of I frames %.2f bytes, %.2f%% of uncompressed\
 picture\n", (float) avI / totalI, ((float) avI / totalI) / uncom_size * 100);
if(totalP)
    printf("Average size of P frames %.2f bytes, %.2f%% of uncompressed\
 picture\n", (float) avP / totalP, ((float) avP / totalP) / uncom_size * 100);
if(totalB)
    printf("Average size of B frames %.2f bytes, %.2f%% of uncompressed\
 picture\n", (float) avB / totalB, ((float) avB / totalB) / uncom_size * 100);
printf("Modal average number of pictures per GOP");
display_modal_gop_average();
printf("Mean average number of pictures per GOP %.2f\n", (float)
    totalPictures / totalGops);
printf("Average size of GOPs %.2f\n", (float) avGop / totalGops);
}


void output_gop_header()
{
long int i, index;
pictureInfo *infoPtr;
   
/* Before the header is output, two rather messy special cases related
  to open Gops and the broken link bit have to be handled */
if(!closed_gop  && valid_index(gopPictureStart, &index))
    {
    infoPtr = &pictureTable[index / MAX_PICTURES][index % MAX_PICTURES];
    if((infoPtr->type == B_FRAME) && !infoPtr->deleted)
        /* Outputing a B frame dependent on previous Gop */
        {
        for(i = gopPictureStart - 1; i >= 0; i --)
            {
            if(!valid_index(i, &index))
                continue;
            infoPtr = &pictureTable[index/ MAX_PICTURES][index % MAX_PICTURES];
            if((infoPtr->type == P_FRAME) || (infoPtr->type == I_FRAME))
                {
                if(infoPtr->deleted)
                    broken_link = TRUE;
                break;
                }
            }
        }
    else
        /* We are not outputing a B frame dependent on the previous Gop. Make
           the Gop closed and reset the broken link bit */
       {
       closed_gop = TRUE;
       broken_link = FALSE;
       }
   }
put_filler();
put_bits(32, GROUP_START_CODE);
put_bits(25, time_code);
put_bit(closed_gop);
put_bit(broken_link);
/* Group extension data */
if(gop_ext_data)
    {
    put_filler();
    put_bits(32, EXTENSION_START_CODE);
    for(i = 0; i < gop_ext_data; i++)
        put_byte(gop_ext[i]);
    }
/* Group user data */
if(gop_user_data)
    {
    put_filler();
    put_bits(32, USER_DATA_START_CODE);
    for(i = 0; i < gop_user_data; i++)
        put_byte(gop_user[i]);
    }
put_filler();
notOutputGopHeader = FALSE;
}


/* valid_index is used to determine if a displayTable index is a valid index
into the pictureTable */
long int valid_index(display, decode)
long int display, *decode;
{
if(displayTable[display / MAX_PICTURES])
    {
    *decode = displayTable[display / MAX_PICTURES][display % MAX_PICTURES];
    return (*decode != -1) && (*decode < totalPictures);
    }
return FALSE;
}



void process_sequence(gopEnd)
long int gopEnd;
{
/* This procedure processes a buffered sequence of pictures.  If no
file output is being performed, the sequence is checked for validity
only.  Otherwise, the appropriate frames are deleted, and the sequence
output */
long int i, deletedP, strt, end, index, j;
pictureInfo *p;
unsigned char *ptr;

strt = lastPictureStored;
end = gopEnd ? totalPictures - 1: totalPictures - 2;

/*  Run through sequence in display order.  Check that all displayTable
references have been filled in */
for(i = strt; i <= end; i ++)
    if(!valid_index(i, &index))
        fprintf(stderr, "\nPicture temporal reference count rubbish, or \
missing picture in sequence (at frame %d decode order)\n", i);

/* Check sequence conforms to various MPEG properties */
if(gopPictureStart == strt)  /* Start of GOP */
    {
    if(pictureTable[strt / MAX_PICTURES][strt % MAX_PICTURES].type != I_FRAME)
        fprintf(stderr, "\nGOP should start with I frame in decode order\n");
    if(valid_index(strt, &index))
        {    
        p = &pictureTable[index / MAX_PICTURES][index % MAX_PICTURES];
        if((p->type == I_FRAME) && !closed_gop)
            fprintf(stderr, "\nOpen GOP found with an I start frame in \
display order\n");
        if((p->type == B_FRAME) && closed_gop)
            fprintf(stderr, "\nClosed GOP found with a B start frame in \
display order\n");
        if((p->type != I_FRAME) && (p->type != B_FRAME))
            fprintf(stderr, "\nGOP should start with an I or B frame in \
display order\n");
        }
    }      
if(gopEnd && valid_index(end, &index))
    {
    p = &pictureTable[index / MAX_PICTURES][index % MAX_PICTURES];
    if((p->type != I_FRAME) && (p->type != P_FRAME))
        fprintf(stderr, "\nGOP should end with an I or P frame in display \
order\n");
    }

/* Adjust start and end frame reference points to nearest frame sequence
   boundaries */
if((strt >= startFrame) && ((endFrame == 0) || (end <= endFrame)))
    {
    if(actualStart == -1) actualStart = strt;
    actualEnd = end;
    }
if(!output)
    return;
              
/*  Determine which frames need to be deleted (either because they are to be
deleted, or because frames they depend on have been deleted.  Run through in
decode order */

deletedP = FALSE;
for(i = strt; i <= end; i ++)
    {
    p = &pictureTable[i / MAX_PICTURES][i % MAX_PICTURES];
    if((strt < startFrame) || ((endFrame > 0) && (end > endFrame)))
        p->deleted = TRUE;
    if((p->type == P_FRAME) && noP)
        p->deleted = deletedP = TRUE;
    if((p->type == B_FRAME) && (noB || deletedP))
        p->deleted = TRUE;
    }
/* Update temporal reference counts.  Run through in display order */
for(i = strt; i <= end; i ++)
    {
    if(!valid_index(i, &index))
        continue;
    p = &pictureTable[index / MAX_PICTURES][index % MAX_PICTURES];
    if(p->deleted)
        temporal_offset ++;
    else
        p->data->temporal_reference -= temporal_offset;
    }
/* Output frames.  Run through in decode order */
for(i = strt; i <= end; i ++)
    {
    p = &pictureTable[i / MAX_PICTURES][i % MAX_PICTURES];
    if(!p->deleted)
        {
        if(notOutputSequenceHeader)
            output_sequence_header();
        if(notOutputGopHeader)
            output_gop_header();
        ptr = p->data->bytes;
        put_bits(32, PICTURE_START_CODE);
        put_bits(10, p->data->temporal_reference);
        put_bits(3, p->type);
        put_bits(3, p->data->fragment);
        for(j = 0; j < p->data->picture_size; j++)
            put_byte(*ptr++);
        }
#ifdef debug
    printf("\nDeleting memory for picture %d\n", i);
#endif    
    free(p->data);
    p->data = NULL;
    }
}     

   
void parse_mpeg()
{
/* Initialise input  buffer  */
bytesToRead = 0;
get_next_data();
next_start_code();
do  {
    get_sequence_header();
    if(fullStatistics)
        print_sequence_header();
    do  {
        get_group_of_pictures();
        if(fullStatistics)
            print_gop();
        if(frames && totalPictures >= frames)
            return;
        } while(next_bits(32, GROUP_START_CODE));
    } while(next_bits(32, SEQUENCE_HEADER_CODE));
if(!next_bits(32, SEQUENCE_END_CODE))
    error("SEQUENCE END CODE is missing.  MPEG file is corrupted");
}


main(argc, argv)
long int argc;
char *argv[];
{
long int arg;

if(argc < 2)
    {
    fprintf(stderr, "SYNTAX: %s mpeg_file1 mpeg_file2 ... [options]\n", argv[0]);
    fprintf(stderr, "\nOptions are:\n");
    fprintf(stderr, "    -full               Generate full MPEG file\
 statistics\n");
    fprintf(stderr, "    -frames n           Only read the first n frames\
 from the input file(s)\n"); 
    fprintf(stderr, "    -output filename    Write MPEG data to\
 filename.  Most usefully used\n                        in conjunction with\
 the following options\n");
    fprintf(stderr, "    -start frame        Start frame display/output\
 from this frame\n");
    fprintf(stderr, "    -end frame          Stop frame display/output at\
 this frame\n");
    fprintf(stderr, "    -noP                Delete all P frames in output\
 file.  All dependent\n                        B frames are also removed\n");
    fprintf(stderr, "    -noB                Delete all B frames in output\
 file\n");
    fprintf(stderr, "    -author             Where to send comments etc.\n"
    );
    exit(1);
    }

fullStatistics = output = startFrame = endFrame = noP = noB = frames = FALSE;
for(i = 1; i < argc; i++)
    if(!strcmp(argv[i], "-full"))
        fullStatistics = TRUE;
    else if(!strcmp(argv[i], "-frames"))
        {
        if(++i == argc)
            error("Frame count expected");
        frames = atoi(argv[i]);
        }
    else if(!strcmp(argv[i], "-output"))
        {
        output = TRUE;
        if(++i == argc)
            error("Output file name expected");
        outputFilename = argv[i];
        }
    else if(!strcmp(argv[i], "-start"))
        {
        if(++i == argc)
            error("Frame number expected");
        startFrame = atoi(argv[i]);
        }
    else if(!strcmp(argv[i], "-end"))
        {
        if(++i == argc)
            error("Frame number expected");
        endFrame = atoi(argv[i]);
        }
    else if(!strcmp(argv[i], "-noP"))
        noP = TRUE;
    else if(!strcmp(argv[i], "-noB"))
        noB = TRUE;
    else if(!strcmp(argv[i], "-author"))
        {
        printf("mpegUtil developed by Phillip Lougher, copyright 1995\n");
        printf("Send comments, bug reports etc. to phillip@comp.lancs.ac.uk\n"
        );
        exit(0);
        }
    else if(*argv[i] == '-')
        {
        fprintf(stderr, "Unrecognised option\n");
        exit(1);
        }

if(output && ((output_file = fopen(outputFilename, "wb")) == NULL))
    {
    perror("Cannot open output file");
    exit(1);
    }

actualStart = -1;

/* Initialise output buffer  */
output_byte = output_buffer;
output_bit = 7;
bytesOutput = 0;
avI = avB = avP = avGop = totalI = totalP = totalB = totalPictures =
    totalGops = totalOpen = totalClosed = gop_array_total = 0;
/* Initialise picture and display lists (these keep track of picture data) */
for(i = 0; i < MAX_BLOCKS; i++)
    pictureTable[i] = (pictureInfo *) (displayTable[i] = NULL);

for(arg = 1; (arg < argc) && (!frames || totalPictures < frames); arg++)
    if(!strcmp(argv[arg], "-output") || !strcmp(argv[arg], "-start") ||
    !strcmp(argv[arg], "-end") || !strcmp(argv[arg], "-frames"))
        arg++;
    else if(*argv[arg] != '-')
        {   
        if((input_file = fopen(argv[arg], "rb")) == NULL)
            {
            perror("Error in opening file for reading");
            exit(1);
            }

        /* Parse MPEG file */
        printf("Parsing file %s.  Please wait...\n", argv[arg]);
        parse_mpeg();
        putchar('\n');
        fclose(input_file);
        }
displayStats();
if(output)
    {
    put_bits(32, SEQUENCE_END_CODE);
    if(bytesOutput && (fwrite(output_buffer, 1, bytesOutput, output_file)
                                < 4))
        {
        perror("Cannot output to output file\n");
        exit(1);
        }
    fclose(output_file);
    }
}
