singe/vldp2/vldp/mpegscan.c
2019-11-11 14:53:02 -06:00

359 lines
9.6 KiB
C

/*
* mpegscan.c
*
* Copyright (C) 2001 Matt Ownby
*
* This file is part of VLDP, a virtual laserdisc player.
*
* VLDP 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.
*
* VLDP 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
*/
///////////////////////////
/*
Some mpeg header information that may be useful at some point.
---------------------------
PICTURE_CODING_EXT DETAILS:
---------------------------
Sample mpeg2 byte sequence: 00 00 01 B5 83 4F F3 59 80
the 8 in 0x83 = picture_coding_ext (in header.c)
the 3 in 0xF3 means FRAME_PICTURE (progressive, not a field)
if the high bit is set (for the byte that is 0x80) then it means the
picture is a progressive frame (I think!)
---------------------
SEQUENCE_EXT DETAILS:
---------------------
Sample mpeg2 byte sequence: 00 00 01 B5 14 8A 00 01 00 00
1 in 0x14 = sequence_ext (in header.c)
bit 3 in 8A set means progressive frame, clear means non-progressive (I
think!)
bits 1 and 2: 00 = invalid, 01 (2) = 4:2:0, 10 (4) = 4:2:2
---------------------
SEQUENCE_DISPLAY_EXT DETAILS:
---------------------
Sample mpeg2 byte sequence: 00 00 01 B5 25 06 06 06 0B 42 0F 00
2 in 0x25 = sequence_display_ext (in header.c)
*/
#include <stdio.h>
#include <stdlib.h> // for malloc
#include "mpegscan.h"
unsigned char g_last_three[3] = { 0 }; // the last 3 bytes read
unsigned int g_last_three_loc[3] = { 0 }; // the position of the last 3 bytes read
int g_last_three_pos = 0;
int g_iframe_count = 0;
int g_pframe_count = 0;
int g_bframe_count = 0;
int g_gop_count = 0; // group of picture count
int g_curframe = 0;
unsigned int g_goppos = 0;
unsigned int g_filepos = 0; // where we are in the file
unsigned int g_frame_type = 0; // I, P, B frame, etc
unsigned int g_last_header_pos = 0; // the position of the last header we've parsed
int g_fields_detected = 0; // whether the stream uses fields
int g_frames_detected = 0; // whether the stream uses frames (these are both here to detect errors)
enum { IN_NOTHING, IN_PIC, IN_PIC_EXT };
int g_status = 0; // whether we are in a special area (inside a picture header, for example)
int g_rel_pos = 0; // which byte of the special area we are in (relative position)
// 1 = sequence_ext, 2 = sequence_display_ext, 8 = picture_coding_ext
unsigned char g_ext_type = 0;
/////////////////////////////////////////////////
// resets all state variables to their initial values. This needs to be called every time an mpeg is parsed
void init_mpegscan()
{
int i = 0;
// clear arrays
for (i = 0; i < 3; i++)
{
g_last_three[i] = 0;
g_last_three_loc[i] = 0;
}
g_last_three_pos = 0;
g_iframe_count = 0;
g_pframe_count = 0;
g_bframe_count = 0;
g_gop_count = 0; // group of picture count
g_curframe = 0;
g_goppos = 0;
g_filepos = 0; // where we are in the file
g_frame_type = 0;
g_last_header_pos = 0;
g_status = 0;
g_rel_pos = 0;
g_fields_detected = 0; // assume we don't use fields
g_frames_detected = 0; // " " " frames
g_ext_type = 0;
}
// return the last 3 bytes that have been read, storing them in a, b, and c
// a is the most recent byte, c is the oldest
// for example, a header of 0 0 1 would be a = 1, b = 0, c = 0
// header is the position of the 'c' byte (oldest byte)
void get_last_three(unsigned char *a, unsigned char *b, unsigned char *c, unsigned int *header)
{
int count = 0;
unsigned char result[3] = { 0 };
int pos = g_last_three_pos;
while (count < 3)
{
pos--;
if (pos < 0)
{
pos = 2;
}
result[count] = g_last_three[pos];
*header = g_last_three_loc[pos]; // this works out so that the oldest value is the final value
count++;
}
*a = result[0]; // newest
*b = result[1];
*c = result[2]; // oldest
}
// updates the last 3 values that we've read, replacing the oldest one with 'val'
// pos is the position of the 'val' in the file
void add_to_last_three(unsigned char val, unsigned int pos)
{
g_last_three[g_last_three_pos] = val;
g_last_three_loc[g_last_three_pos] = pos;
g_last_three_pos++;
if (g_last_three_pos > 2)
{
g_last_three_pos = 0;
}
}
// parses from file stream, length # of bytes
// writes results to the open datafile
// returns stat codes
int parse_video_stream(FILE *datafile, unsigned int length)
{
int result = P_IN_PROGRESS;
int ch = 0;
unsigned int start_pos = g_filepos;
const int minus_one = -1;
unsigned int buf_index = 0;
size_t bytes_read = 0;
unsigned char *buf = (unsigned char *) malloc(length); // allocate a chunk of memory to read in file
if (!buf)
{
return P_ERROR;
}
bytes_read = io_read(buf, length); // read in a chunk
// parse this chunk of video
while (g_filepos - start_pos < length)
{
// this should always be true unless we hit EOF
if (buf_index < bytes_read)
{
g_filepos++;
g_rel_pos++;
ch = buf[buf_index++];
}
// if we've hit EOF
else
{
// if we're certain we're using fields
if ((g_fields_detected) && (!g_frames_detected))
{
result = P_FINISHED_FIELDS;
}
// else if we're certain we're not using fields
// (for mpeg1 g_fields_detected and g_frames_detected will both be 0)
else if (!g_fields_detected)
{
result = P_FINISHED_FRAMES;
}
// else we can't determine what's going on, so do an error to be safe
else
{
result = P_ERROR;
}
break; // end loop
}
// We must make sure we only process 1 byte per iteration of this loop so that we don't
// exceed the maximum length.
// if we are in the middle of a frame header
if (g_status == IN_PIC)
{
// if we need the first byte following a frame header
if (g_rel_pos == 0)
{
g_frame_type = ch << 8;
}
// else if we need the second byte following a frame header
else if (g_rel_pos == 1)
{
g_frame_type = g_frame_type | ch;
g_frame_type = (g_frame_type >> 3) & 3; // isolate frame type
// examine which type of frame we've found
switch (g_frame_type)
{
case 1: // I frame
g_iframe_count++;
fwrite(&g_last_header_pos, sizeof(g_last_header_pos), 1, datafile); // actual beginning of I frame
#ifdef VLDP_DEBUG
// fprintf(stderr, "Found an I frame at %x\n", g_last_header_pos);
#endif
break;
default: // if it's not an I frame, just write -1
fwrite(&minus_one, sizeof(minus_one), 1, datafile);
#ifdef VLDP_DEBUG
// fprintf(stderr, "Found a non I frame at %x\n", g_last_header_pos);
#endif
break;
}
g_status = IN_NOTHING; // we got what we came for, now get it :)
} // end if we are on the second byte of the picture
} // end if we're in a frame header
// if we're in a picture header extension ...
else if (g_status == IN_PIC_EXT)
{
// if we're about to get the EXT type
if (g_rel_pos == 0)
{
g_ext_type = ch >> 4;
}
// UPDATE : It seems that this information is just a hint of whether the mpeg is interlaced or progressive, and
// may be wrong, so we cannot rely on this information.
/*
// if we have ext type 1 (sequence_ext) then we can see if it's frames or fields
else if ((g_rel_pos == 1) && (g_ext_type == 1))
{
// are we progressive?
if (ch & 8)
{
g_frames_detected = 1;
}
// else we're using fields
else
{
g_fields_detected = 1;
}
g_status = IN_NOTHING;
}
*/
// this is where we either find out if we're using fields/frames or eject
else if (g_rel_pos >= 2)
{
// if we have ext type 8, then we can see if this uses frames or fields
if ((g_rel_pos == 2) && (g_ext_type == 8))
{
unsigned char u8Val = ch & 3;
// we need to detect whether the stream uses fields so we can adjust our searches accordingly
// 1 is the code for TOP FIELD, 2 is the code for BOTTOM_FIELD
if ((u8Val == 1) || (u8Val == 2))
{
g_fields_detected = 1;
}
// 3 is code for a full image
else if (u8Val == 3)
{
g_frames_detected = 1;
}
} // end if ext type is 8 ...
// else other ext type which we ignore ...
// when we get this far, we are done parsing EXT ...
g_status = IN_NOTHING;
}
}
// if we are in nothing, looking for a new header
else
{
unsigned char header[3] = { 0 };
get_last_three(&header[0], &header[1], &header[2], &g_last_header_pos);
// if we're at a place where a header is
if ((header[2] == 0) && (header[1] == 0) && (header[0] == 1))
{
// see what type of header this is
switch (ch)
{
case 0: // video frame
g_curframe++; // advance frame pointer
g_rel_pos = -1; // this gets incremented to 0 before we check, and I wanted 0 to mean 1st byte
g_status = IN_PIC;
break;
case 0xB3: // sequence header
break;
case 0xB5: // extension header
g_rel_pos = -1;
g_status = IN_PIC_EXT;
break;
case 0xB8: // Group of Picture
g_goppos = g_last_header_pos;
g_gop_count++;
#ifdef VLDP_DEBUG
fprintf(stderr, "Got GOP at %x\n", g_goppos);
#endif
break;
default:
break;
} // end switch
} // end if we found a header
} // end if we are looking for a new header
add_to_last_three((unsigned char) ch, g_filepos - 1);
} // end while we're not done with video stream
free(buf); // de-allocate buffer
return result;
}