359 lines
9.6 KiB
C
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;
|
|
|
|
}
|