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

1389 lines
42 KiB
C

/*
* vldp_internal.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
*/
// contains the function for the VLDP private thread
#include <stdio.h>
#include <stdlib.h> // for malloc/free
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
//#include <unistd.h>
//#include "inttypesreplace.h"
#include <SDL.h>
#include "vldp_internal.h"
#include "vldp_common.h"
#include "mpegscan.h"
#ifdef WIN32
#include "../vc++/inttypes.h"
#else
#include <inttypes.h>
#endif
#include "mpeg2.h"
#include "video_out.h"
/////////////////////////////
// NOTICE : these variables should only be used by the private thread !!!!!!!!!!!!
Uint8 s_old_req_cmdORcount = CMDORCOUNT_INITIAL; // the last value of the command byte we received
int s_paused = 0; // whether the video is to be paused
int s_step_forward = 0; // whether to step 1 frame forward
int s_blanked = 0; // whether the mpeg video is to be blanked
int s_frames_to_skip = 0; // how many frames to skip before rendering the next frame (used for P and B frames seeking)
int s_frames_to_skip_with_inc = 0; // // how many frames to skip while increasing the frame number (for multi-speed playback)
int s_skip_all = 0; // if we are to skip any frame to be displayed (to avoid seeing frames we shouldn't)
// How many frames we've skipped when s_skip_all is enabled.
// (for performance tuning, we want to minimize this number)
unsigned int s_uSkipAllCount = 0;
Uint32 s_timer = 0; // FPS timer used by the blitting code to run at the right speed
// any extra delay that null_draw_frame() will use before drawing a frame (intended for laserdisc seek delay simulation)
// NOTE : this value gets reset to 0 after it has been 'used'
Uint32 s_extra_delay_ms = 0;
// how many frames must have been displayed (relative to s_timer!!!)
// before we advance to the next frame
Uint32 s_uFramesShownSinceTimer = 0;
// which frame we've skipped to (0 if there is nothing pending)
unsigned int s_uPendingSkipFrame = 0;
// multi-speed variables ...
unsigned int s_skip_per_frame = 0; // how many frames to skip per frame (for playing at 2X for example)
unsigned int s_stall_per_frame = 0; // how many frames to stall per frame (for playing at 1/2X for example)
// pre-cache variables
#define MAX_PRECACHE_FILES 300 /* maximum number of files that we'll pre-cache */
VLDP_BOOL s_bPreCacheEnabled = VLDP_FALSE; // whether precaching is currently enabled
unsigned int s_uCurPreCacheIdx = 0; // if pre-caching is enabled, which index is currently being used
unsigned int s_uPreCacheIdxCount = 0; // how many files have been precached
struct precache_entry_s s_sPreCacheEntries[MAX_PRECACHE_FILES]; // struct array holding precache data
#define MAX_LDP_FRAMES 430000 // rdg2010: Anbou 4 hours at 30 frames per second
static FILE *g_mpeg_handle = NULL; // mpeg file we currently have open
static mpeg2dec_t *g_mpeg_data = NULL; // structure for libmpeg2's state
static vo_instance_t *s_video_output = NULL;
static Uint32 g_frame_position[MAX_LDP_FRAMES] = { 0 }; // the file position of each I frame
static Uint32 g_totalframes = 0; // total # of frames in the current mpeg
#define BUFFER_SIZE 524288 //262144 rdg2010
static Uint8 g_buffer[BUFFER_SIZE]; // buffer to hold mpeg2 file as we read it in
#define HEADER_BUF_SIZE 200
static Uint8 g_header_buf[HEADER_BUF_SIZE];
static unsigned int g_header_buf_size = 0; // size of the header buffer
// how many frames we will stall after beginning playback (should be 1, because presumably before we start playing,
// the disc has been paused showing the same frame, and we want the frame to display 1 more frame before moving
// to the next one. This behavior is somewhat arbitrary to act like we think laserdisc players act, but this
// value could also be proven to be 0 if someone does future research.)
#define PLAY_FRAME_STALL 1
////////////////////////////////////////////////
// this is our video thread which gets called
int idle_handler(void *surface)
{
int done = 0;
s_video_output = (vo_instance_t *) vo_null_open(); // open 'null' driver (we just pass decoded frames to parent thread)
if (s_video_output)
{
g_mpeg_data = mpeg2_init();
}
else
{
fprintf(stderr, "VLDP : Error opening LIBVO!\n");
done = 1;
}
// unless we are drawing video to the screen, we just sit here
// and listen for orders from the parent thread
while (!done)
{
// while we have received new commands to be processed
// (so we don't go to sleep on skips)
while (ivldp_got_new_command() && !done)
{
// examine the actual command (strip off the count)
switch(g_req_cmdORcount & 0xF0)
{
case VLDP_REQ_QUIT:
done = 1;
break;
case VLDP_REQ_OPEN:
idle_handler_open();
break;
case VLDP_REQ_PRECACHE:
idle_handler_precache();
break;
case VLDP_REQ_PLAY:
idle_handler_play();
break;
case VLDP_REQ_SEARCH:
idle_handler_search(0);
break;
case VLDP_REQ_SKIP:
idle_handler_search(1);
break;
case VLDP_REQ_PAUSE: // pause command while we're already idle? this is an error
case VLDP_REQ_STOP: // stop command while we're already idle? this is an error
g_out_info.status = STAT_ERROR;
ivldp_ack_command();
break;
case VLDP_REQ_LOCK:
ivldp_lock_handler();
break;
default:
fprintf(stderr, "VLDP WARNING : Idle handler received command which it is ignoring\n");
break;
} // end switch
SDL_Delay(0); // give other threads some breathing room (but not much hehe)
} // end if we got a new command
g_in_info->render_blank_frame(); // This makes sure that the video overlay gets drawn even if there is no video being played
// we need to delay here because otherwise, this idle loop will execute at 100% cpu speed and really slow things down
// It shouldn't hurt us when we get a command that requires immediate attention (such as skip) because of the
// inner while loop above (while ivldp_got_new_command)
// NOTE : we want to delay for about 1 frame (or field) here
SDL_Delay(16); // 1 field is 16.666ms assuming 60 hz
} // end while we have not received a quit command
io_close();
g_out_info.status = STAT_ERROR;
mpeg2_close(g_mpeg_data); // shutdown libmpeg2
s_video_output->close(s_video_output); // shutdown null driver
// de-allocate any files that have been precached
while (s_uPreCacheIdxCount > 0)
{
--s_uPreCacheIdxCount;
free(s_sPreCacheEntries[s_uPreCacheIdxCount].ptrBuf);
}
ivldp_ack_command(); // acknowledge quit command
return 0;
}
// returns 1 if there is a new command waiting for us or 0 otherwise
int ivldp_got_new_command()
{
int result = 0;
// if they are no longer equal
if (g_req_cmdORcount != s_old_req_cmdORcount)
{
result = 1;
}
return result;
}
// acknowledges a command sent by the parent thread
// NOTE : We don't check to see if parent thread got our acknowledgement because it creates too much latency
void ivldp_ack_command()
{
s_old_req_cmdORcount = g_req_cmdORcount;
g_ack_count++; // here is where we acknowledge
}
void ivldp_lock_handler()
{
#ifdef VLDP_DEBUG
fprintf(stderr, "DBG: VLDP REQ LOCK RECEIVED!!!\n");
#endif
ivldp_ack_command();
{
VLDP_BOOL bLocked = VLDP_TRUE;
// the user should unlock immediately after locking, so we need not check for other commands
while (bLocked == VLDP_TRUE)
{
SDL_Delay(1);
if (ivldp_got_new_command())
{
switch (g_req_cmdORcount & 0xF0)
{
case VLDP_REQ_UNLOCK:
#ifdef VLDP_DEBUG
fprintf(stderr, "DBG: VLDP REQ UNLOCK RECEIVED!!!\n");
#endif
ivldp_ack_command();
bLocked = VLDP_FALSE;
break;
default:
fprintf(stderr, "WARNING : lock handler received a command %x that wasn't to unlock it\n", g_req_cmdORcount);
break;
}
}
}
}
}
// the handler we call if we're paused... called from video_out_sdl
// this function is called from within a while loop
void paused_handler()
{
// the moment we render the still frame, we need to reset the FPS timer so we don't try to catch-up
if (g_out_info.status != STAT_PAUSED)
{
g_out_info.status = STAT_PAUSED;
// reset these vars because otherwise null_draw_frame will loop redundantly for no good reason
s_timer = g_in_info->uMsTimer; // since we have just rendered the frame we searched to, we refresh the timer
s_uFramesShownSinceTimer = 1; // this gives us a little breathing room
#ifdef VLDP_DEBUG
printf("paused_handler() : status set to STAT_PAUSED, s_timer set to %u\n", g_in_info->uMsTimer);
#endif
}
// if we have a new command coming in
if (ivldp_got_new_command())
{
// strip off the count and examine the command
switch (g_req_cmdORcount & 0xF0)
{
case VLDP_REQ_PLAY:
ivldp_respond_req_play();
break;
// if we get any of these commands, we have to skip the remaining buffered frames and reset
case VLDP_REQ_STOP:
case VLDP_REQ_QUIT:
case VLDP_REQ_OPEN:
case VLDP_REQ_SEARCH:
s_skip_all = 1;
s_uSkipAllCount = 0;
// we don't acknowledge these requests here, we let the idle handler take care of them
break;
case VLDP_REQ_STEP_FORWARD:
// if they've also requested a step forward
// NOTE : no need to change status, as we started paused and we'll end paused
ivldp_ack_command();
s_step_forward = 1;
break;
case VLDP_REQ_LOCK:
ivldp_lock_handler();
break;
default: // else if we get a pause command or another command we don't know how to handle, just ignore it
fprintf(stderr, "WARNING : pause handler received command %x that it is ignoring\n", g_req_cmdORcount);
ivldp_ack_command(); // acknowledge the command
break;
} // end switch
} // end if we have a new command coming in
}
// the handler we call if we're playing
// this handler is called from within a while loop
void play_handler()
{
// if we've received a new incoming command
if (ivldp_got_new_command())
{
// strip off count and examine command
switch(g_req_cmdORcount & 0xF0)
{
case VLDP_REQ_NONE: // no incoming command
break;
case VLDP_REQ_PAUSE:
case VLDP_REQ_STEP_FORWARD:
ivldp_respond_req_pause_or_step();
break;
case VLDP_REQ_SPEEDCHANGE:
ivldp_respond_req_speedchange();
break;
case VLDP_REQ_STOP:
case VLDP_REQ_QUIT:
case VLDP_REQ_OPEN:
case VLDP_REQ_SEARCH:
case VLDP_REQ_SKIP:
s_skip_all = 1; // bail out, handle these requests in the idle handler
s_uSkipAllCount = 0;
break;
case VLDP_REQ_LOCK:
ivldp_lock_handler();
break;
default: // unknown or redundant command, just ignore
ivldp_ack_command();
fprintf(stderr, "WARNING : play handler received command which it is ignoring\n");
break;
}
} // end if we got a new command
}
// sets the framerate inside our info structure based upon the framerate code received
void ivldp_set_framerate(Uint8 frame_rate_code)
{
// now to compute the framerate
switch (frame_rate_code)
{
case 1:
g_out_info.uFpks = 23976;
break;
case 2:
g_out_info.uFpks = 24000;
break;
case 3:
g_out_info.uFpks = 25000;
break;
case 4:
g_out_info.uFpks = 29970;
break;
case 5:
g_out_info.uFpks = 30000;
break;
case 6:
g_out_info.uFpks = 50000;
break;
case 7:
g_out_info.uFpks = 59940;
break;
case 8:
g_out_info.uFpks = 60000;
break;
default:
// else we got an invalid frame rate code
fprintf(stderr, "ERROR : Invalid frame rate code!\n");
g_out_info.uFpks = 1000; // to avoid divide by 0 error
break;
} // end switch
// precalculate values that we use over and over again
g_out_info.u2milDivFpks = 2000000 / g_out_info.uFpks;
}
/////////////////
// decode_mpeg2 function taken from mpeg2dec.c and optimized a bit
static void decode_mpeg2 (uint8_t * current, uint8_t * end)
{
const mpeg2_info_t * info;
int state;
vo_setup_result_t setup_result;
mpeg2_buffer (g_mpeg_data, current, end);
info = mpeg2_info (g_mpeg_data);
// loop until we return (state is -1)
for (;;)
{
state = mpeg2_parse (g_mpeg_data);
switch (state)
{
case -1:
return;
case STATE_SEQUENCE:
/* might set nb fbuf, convert format, stride */
/* might set fbufs */
if (s_video_output->setup (s_video_output, info->sequence->width,
info->sequence->height, &setup_result))
{
fprintf (stderr, "display setup failed\n"); // this should never happen
}
if (setup_result.convert)
mpeg2_convert (g_mpeg_data, setup_result.convert, NULL);
// if this driver offers a setup_fbuf callback ...
// else if (s_video_output->setup_fbuf)
// we KNOW we offer a setup_fbuf function, so get rid of this conditional
{
uint8_t * buf[3];
void * id;
s_video_output->setup_fbuf (s_video_output, buf, &id);
mpeg2_set_buf (g_mpeg_data, buf, id);
s_video_output->setup_fbuf (s_video_output, buf, &id);
mpeg2_set_buf (g_mpeg_data, buf, id);
s_video_output->setup_fbuf (s_video_output, buf, &id);
mpeg2_set_buf (g_mpeg_data, buf, id);
}
break;
case STATE_PICTURE:
/* might skip */
/* might set fbuf */
// all possible stuff we could've done here was removed because the null driver
// doesn't do any of it
break;
case STATE_PICTURE_2ND:
/* should not do anything */
break;
case STATE_SLICE:
case STATE_END:
/* draw current picture */
/* might free frame buffer */
// if the init hasn't been called yet, this may fail so we have to put the conditional
if (info->display_fbuf)
{
s_video_output->draw (s_video_output, info->display_fbuf->buf,
info->display_fbuf->id);
}
break;
} // end switch
} // end endless for loop
}
/////////////////
// Pre-caches sequence header so that vldp_process_sequence_header (and thus any seeks) are faster
// NOTE: this does change the file position
void vldp_cache_sequence_header()
{
Uint32 val = 0;
unsigned int index = 0;
io_seek(0); // start at beginning
io_read(g_header_buf, HEADER_BUF_SIZE); // assume that we must find the first frame in this chunk of bytes
// if not, we'll have to increase the number
// go until we have found the first frame or we run out of data
while (val != 0x000001B8)
{
val = val << 8;
val |= g_header_buf[index]; // add newest byte to bottom of val
index++; // advance the end pointer
if (index > HEADER_BUF_SIZE)
{
fprintf(stderr, "VLDP : Could not find first frame in 0x%x bytes. Modify source code to increase buffer!\n", HEADER_BUF_SIZE);
break;
}
}
// subtract 4 because we stopped when we found the 4 byte header of the first frame
g_header_buf_size = index - 4;
}
// feeds libmpeg2 the beginning of the file up to the first Group of Picture
// It needs this info to get setup to play from an arbitrary position in the file that isn't the beginning
// In other words, this is ONLY used when we are seeking to an arbitrary frame
void vldp_process_sequence_header()
{
decode_mpeg2 (g_header_buf, g_header_buf + g_header_buf_size); // decode the pre-cached sequence header
}
// opens a new mpeg2 file
// The file is positioned at the beginning
void idle_handler_open()
{
char req_file[STRSIZE] = { 0 };
unsigned int req_idx = g_req_idx;
VLDP_BOOL req_precache = g_req_precache;
VLDP_BOOL bSuccess = VLDP_FALSE;
SAFE_STRCPY(req_file, g_req_file, sizeof(req_file)); // after we ack the command, this string could become clobbered at any time
// NOTE : it is very important that we change our status to BUSY before acknowledging the command, because
// our previous status could be STAT_ERROR, which causes problems with the *_and_block commands.
g_out_info.status = STAT_BUSY; // make us busy while opening the file
ivldp_ack_command(); // acknowledge open command
// reset libmpeg2 so it is prepared to begin reading from a new m2v file
mpeg2_partial_init(g_mpeg_data);
// if we have previously opened an mpeg, we need to close it and reset
if (io_is_open())
{
io_close();
// since the overlay is double buffered, we want to blank it
// twice before closing it. This is to avoid a 'flicker effect' that we can
// get when switching between mpeg files.
g_in_info->render_blank_frame();
g_in_info->render_blank_frame();
// we need to close this surface, because the new mpeg may have a different resolution than the old one, and therefore,
// the YUV buffer must be re-allocated
s_video_output->close(s_video_output);
}
// if we've been requested to open a real file ...
if (!req_precache)
{
bSuccess = io_open(req_file);
}
// else we've been requested to open a precached file...
else
{
bSuccess = io_open_precached(req_idx);
}
// If file was opened successfully,
// check to make sure it's a video stream and also get framerate
if (bSuccess)
{
Uint8 small_buf[8];
io_read(small_buf, sizeof(small_buf)); // 1st 8 bytes reveal much
// if we find the proper mpeg2 video header at the beginning of the file
if (((small_buf[0] << 24) | (small_buf[1] << 16) | (small_buf[2] << 8) | small_buf[3]) == 0x000001B3)
{
g_out_info.w = (small_buf[4] << 4) | (small_buf[5] >> 4); // get mpeg width
g_out_info.h = ((small_buf[5] & 0x0F) << 8) | small_buf[6]; // get mpeg height
ivldp_set_framerate(small_buf[7] & 0xF); // set the framerate
io_seek(0); // go back to beginning for parser's benefit
// load/parse all the frame locations in the file for super fast seeking
if (ivldp_get_mpeg_frame_offsets(req_file))
{
g_in_info->report_mpeg_dimensions(g_out_info.w, g_out_info.h); // this function creates the video overlay.
// We want to make sure we do this _after_ the frame offsets are loaded in because
// graphics are drawn to the main screen if parsing needs to be done.
vldp_cache_sequence_header(); // cache sequence header for faster seeking
io_seek(0); // seek back to beginning of file
g_out_info.status = STAT_STOPPED; // now that the file is open, we're ready to play
}
else
{
io_close();
fprintf(stderr, "VLDP PARSE ERROR : Is the video stream damaged?\n");
g_out_info.status = STAT_ERROR; // change from BUSY to ERROR
}
} // end if a proper mpeg header was found
// if the file had a bad header
else
{
io_close();
fprintf(stderr, "VLDP ERROR : Did not find expected header. Is this mpeg stream demultiplexed??\n");
g_out_info.status = STAT_ERROR;
}
} // end if file exists
else
{
fprintf(stderr, "VLDP ERROR : Could not open file!\n");
g_out_info.status = STAT_ERROR;
}
#ifdef VLDP_DEBUG
printf("idle_handler_open returning ...\n");
#endif
}
// gets called when the user wants to precache a file ...
void idle_handler_precache()
{
char req_file[STRSIZE] = { 0 };
SAFE_STRCPY(req_file, g_req_file, sizeof(req_file)); // after we ack the command, this string could become clobbered at any time
// always set the status before acknowledging the command so previous status doesn't get through
g_out_info.status = STAT_BUSY; // make us busy while opening the file
ivldp_ack_command();
// if we still have room in our array to precache ...
if (s_uPreCacheIdxCount < MAX_PRECACHE_FILES)
{
// GET LENGTH OF ACTUAL FILE
FILE *F = fopen(req_file, "rb");
if (F)
{
struct stat filestats;
fstat(fileno(F), &filestats); // get stats for file to get file length
s_sPreCacheEntries[s_uPreCacheIdxCount].uLength = filestats.st_size;
s_sPreCacheEntries[s_uPreCacheIdxCount].uPos = 0; // start at the beginning
// allocate RAM to hold file ...
s_sPreCacheEntries[s_uPreCacheIdxCount].ptrBuf = malloc(filestats.st_size);
// if malloc succeeded
if (s_sPreCacheEntries[s_uPreCacheIdxCount].ptrBuf)
{
unsigned char *u8Ptr = (unsigned char *) s_sPreCacheEntries[s_uPreCacheIdxCount].ptrBuf;
unsigned int uTotalBytesRead = 0;
const unsigned int READ_SIZE = 1048576; // how many bytes to read in at a time
g_in_info->report_parse_progress(-1); // notify other thread that we're starting
// load in the file ...
for (;;)
{
unsigned int uBytesRead = 0;
unsigned int uBytesToRead = READ_SIZE;
unsigned int uBytesLeft = filestats.st_size -
uTotalBytesRead;
// don't overflow
if (uBytesToRead > uBytesLeft) uBytesToRead = uBytesLeft;
uBytesRead = (unsigned int) fread(u8Ptr + uTotalBytesRead, 1, uBytesToRead, F);
uTotalBytesRead += uBytesRead;
// if we're done ...
if (uTotalBytesRead >= (unsigned int) filestats.st_size)
{
break;
}
// update user on our precache progress
g_in_info->report_parse_progress((double) uTotalBytesRead /
s_sPreCacheEntries[s_uPreCacheIdxCount].uLength);
}
g_in_info->report_parse_progress(1); // notify other thread that we're done ...
// notify other thread of which index we've used to precache this file
g_out_info.uLastCachedIndex = s_uPreCacheIdxCount;
// we're done with this entry, so the count increases
// (this must be done after we've read in the file so that the index is correct for that operation)
++s_uPreCacheIdxCount;
g_out_info.status = STAT_STOPPED; // success
}
// else malloc failed
else
{
g_out_info.status = STAT_ERROR;
}
fclose(F);
}
// else we couldn't open the file
else
{
g_out_info.status = STAT_ERROR;
}
}
// else we're out of room, so return an error
else
{
g_out_info.status = STAT_ERROR;
}
}
// starts playing the mpeg from the very beginning
// This is ONLY called when a file has just been opened and no seeking has taken place,
// OR if ivldp_render() has hit EOF and rewound back to the beginning
// This ASSUMES that g_mpeg_handle is at the BEGINNING of the file
// and that mpeg2_partial_init() has been called
void idle_handler_play()
{
ivldp_respond_req_play();
// when the frame is actually blitted is when we set the status to STAT_PLAYING
ivldp_render();
}
// responds to play request
void ivldp_respond_req_play()
{
s_timer = g_req_timer;
#ifdef VLDP_DEBUG
fprintf(stderr, "ivldp_respond_req_play() : g_req_timer is %u, and uMstimer is %u\n", g_req_timer, g_in_info->uMsTimer); // REMOVE ME
#endif // VLDP_DEBUG
s_uFramesShownSinceTimer = PLAY_FRAME_STALL; // we want to render the currently shown frame for 1 frame before moving on
g_out_info.status = STAT_PLAYING; // we strive for instant response (and catch-up to maintain timing)
ivldp_ack_command(); // acknowledge the play command
s_paused = 0; // we to not want to pause on 1 frame
s_blanked = 0; // we want to see the video
s_frames_to_skip = s_frames_to_skip_with_inc = 0; // skip no frames, just play from current position
// this value is reset again as soon as we confirm that we are playing
}
// gets called if ivldp_got_new_command() returns true and the new command
// is either a pause or a step
void ivldp_respond_req_pause_or_step()
{
// if they've also requested a step forward
if ((g_req_cmdORcount & 0xF0) == VLDP_REQ_STEP_FORWARD)
{
s_step_forward = 1;
}
// NOTE : by design, our status should not change until paused_handler is called, so we leave it at PLAYING for now
ivldp_ack_command();
#ifdef VLDP_DEBUG
printf("VLDP_REQ_PAUSED received when frame is %u, uMsTimer is %u\n", g_out_info.current_frame,
g_in_info->uMsTimer); // DBG REMOVE ME!@
#endif // VLDP_DEBUG
s_paused = 1;
s_blanked = 0;
}
// gets called if ivldp_got_new_command() returns true and the new command
// is a speed change command
void ivldp_respond_req_speedchange()
{
s_skip_per_frame = g_req_skip_per_frame;
s_stall_per_frame = g_req_stall_per_frame;
ivldp_ack_command();
}
// displays 1 or more frames to the screen, according to the state variables.
// This function can be used to do both still frames and moving video. Play and search both use this function.
void ivldp_render()
{
Uint8 *end = NULL;
int render_finished = 0;
#ifdef VLDP_BENCHMARK
Uint32 render_start_time = SDL_GetTicks(); // keep track of when we started
Sint16 render_start_frame = g_out_info.current_frame; // keep track of the frame we started timing stuff on
double total_seconds = 0.0; // used to calculate FPS
Sint16 total_frames = 0; // used to calculate FPS
FILE *F = fopen("benchmark.txt", "wt"); // create a little logfile for benchmark results
#endif
s_skip_all = 0; // default value, don't skip frames unless the play or paused handler orders it
#ifdef VLDP_DEBUG
printf("s_skip_all skipped %u frames.\n", s_uSkipAllCount);
#endif // VLDP_DEBUG
// check to make sure a file has been opened
if (!io_is_open())
{
render_finished = 1;
fprintf(stderr, "VLDP RENDER ERROR : we tried to render an mpeg but none was open!\n");
g_out_info.status = STAT_ERROR;
}
// while we're not finished playing and pausing
while (!render_finished)
{
// end = g_buffer + fread (g_buffer, 1, BUFFER_SIZE, g_mpeg_handle);
end = g_buffer + io_read(g_buffer, BUFFER_SIZE);
// safety check, they could be equal if we were already at EOF before we tried this
if (g_buffer != end)
{
// read chunk of video stream
decode_mpeg2 (g_buffer, end); // display it to the screen
}
// if we've read to the end of the mpeg2 file, then we can't play anymore, so we pause on last frame
if (end != (g_buffer + BUFFER_SIZE))
{
g_out_info.status = STAT_STOPPED; // it's a toss-up between this and STAT_PAUSED
render_finished = 1;
// reset libmpeg2 so it is prepared to begin reading from the beginning of the file
mpeg2_partial_init(g_mpeg_data);
io_seek(0); // seek to the beginning of the file
g_out_info.current_frame = 0; // set frame # to beginning of file where it belongs
}
// if a new command is coming in, check to see if we need to stop
if (ivldp_got_new_command())
{
// check to see if we need to suddenly abort the rendering process
switch (g_req_cmdORcount & 0xF0)
{
case VLDP_REQ_QUIT:
case VLDP_REQ_OPEN:
case VLDP_REQ_SEARCH:
case VLDP_REQ_STOP:
g_out_info.status = STAT_BUSY;
render_finished = 1;
break;
case VLDP_REQ_SKIP:
// do not change the playing status because skips are supposed to be instant
render_finished = 1;
break;
} // end switch
} // end if they got a new command
} // end while
#ifdef VLDP_BENCHMARK
fprintf(F, "Benchmarking result:\n");
total_frames = g_out_info.current_frame - render_start_frame;
total_seconds = (SDL_GetTicks() - render_start_time) / 1000.0;
fprintf(F, "VLDP displayed %u frames (%d to %d) in %f seconds (%f FPS)\n",
total_frames, render_start_frame, g_out_info.current_frame, total_seconds, total_frames / total_seconds);
fclose(F);
#endif
}
#ifdef VLDP_DEBUG
#ifdef UNIX
#include <signal.h> // to break into debugger
#endif // UNIX
#endif // VLDP_DEBUG
// searches to any arbitrary frame, be it I, P, or B, and renders it
// if skip is set, it will do a laserdisc skip instead of a search (ie it will go a frame, resume playback,
// and not adjust any timers)
void idle_handler_search(int skip)
{
unsigned int proposed_pos = 0;
Uint32 req_frame = g_req_frame; // after we acknowledge the command, g_req_frame could become clobbered
Uint32 min_seek_ms = g_req_min_seek_ms; // g_req_min_seek_ms can be clobbered at any time after we acknowledge command
// adjusted req frame is the requested frame with fields taken into account
unsigned int uAdjustedReqFrame = 0;
unsigned int actual_frame = 0;
int skipped_I = 0;
// status must be changed before acknowledging command, because previous status could be STAT_ERROR, which
// causes problems with *_and_block vldp API commands.
if (!skip) g_out_info.status = STAT_BUSY;
// else we're skipping
// (our status is already STAT_PLAYING so we don't need to set it)
else
{
// Since we're skipping, we must re-calculate s_uFramesShownSinceTimer because it is possible on laggy systems for
// this value to be behind what it ought to be, and we need to be perfectly in sync with the parent thread's
// g_in_info->uMsTimer value in order to display the correct frames.
// NOTE : this _must_ happen before we acknowledge the command, because we need g_in_info->uMsTimer to be constant while
// we recalculate s_uFramesShownSinceTimer
// NOTE 2 : we must use Uint64 here because otherwise this will overflow sometime after 2 minutes which DOES happen
// on Astron Belt if 'infinite timer' is enabled (it doesn't do any searches, just skips)
unsigned int uNewFramesShownSinceTimer = (unsigned int) ((((Uint64) (g_in_info->uMsTimer - s_timer)) * g_out_info.uFpks) / 1000000);
// take into account the extra frame we did when playback started
uNewFramesShownSinceTimer += PLAY_FRAME_STALL;
#ifdef VLDP_DEBUG
// break into debugger if this happens so we can examine what's going on
if (uNewFramesShownSinceTimer != s_uFramesShownSinceTimer)
{
printf("skip timing off: uNewFramesShownSinceTimer is %u, s_uFramesShownSinceTimer is %u\n",
uNewFramesShownSinceTimer, s_uFramesShownSinceTimer);
// this shouldn't ever happen
if (s_uFramesShownSinceTimer > uNewFramesShownSinceTimer)
{
fprintf(stderr, "!!! s_uFramesShownSinceTimer > uNewFramesShownSinceTimer, this shouldn't happen!\n");
#ifdef UNIX
raise(SIGTRAP); // we wanna know what's going on ...
#endif // UNIX
}
}
#endif // VLDP_DEBUG
s_uFramesShownSinceTimer = uNewFramesShownSinceTimer;
}
ivldp_ack_command(); // acknowledge search/skip command
// reset libmpeg2 so it is prepared to start from a new spot
mpeg2_partial_init(g_mpeg_data);
vldp_process_sequence_header(); // we need to process the sequence header before we can jump around the file for frames
// if we're doing a search...
if (!skip)
{
s_paused = 1; // we do want to pause on the frame we search to
s_timer = g_in_info->uMsTimer; // reset timer so framerate is correct
// NOTE : resetting s_timer here doesn't do much if we aren't simulating
// artificial seek delay, because paused_handler() will reset it again anyway.
// (but it is important for seek delay!)
s_uFramesShownSinceTimer = 0;
// NOTE : by setting this to 0, we are eliminating any potential unnecessary
// delays before paused_handler() gets called, which is what we want.
s_extra_delay_ms = min_seek_ms; // any extra delay we have will be when the parent thread requested (this can be 0)
// if we are to blank during searches ...
if (g_in_info->blank_during_searches)
{
g_in_info->render_blank_frame();
}
}
// if we are skipping we are actually in the middle of playback so we don't reset the timer
else
{
// NOTE : we don't want to change the status here because
// skipping is supposed to be instantaneous
s_paused = 0;
// if we need to blank frame for skipping
if (g_in_info->blank_during_skips)
{
g_in_info->render_blank_frame();
}
}
// adjusted req frame is the requested frame with fields taken into account
uAdjustedReqFrame = req_frame;
// if we're using fields, then the requested frame must be doubled (2 fields per frame)
if (g_out_info.uses_fields) uAdjustedReqFrame <<= 1;
actual_frame = uAdjustedReqFrame;
// do a bounds check
if (uAdjustedReqFrame < g_totalframes)
{
proposed_pos = g_frame_position[uAdjustedReqFrame]; // get the proposed position
#ifdef VLDP_DEBUG
printf("Initial proposed position is : %x\n", proposed_pos);
#endif
s_frames_to_skip = s_frames_to_skip_with_inc = 0; // the below problem is no longer a problem
// loop until we find which position in the file to seek to
for (;;)
{
// if the frame we want is not an I frame, go backward until we find an I frame, and increase # of frames to skip forward
while ((proposed_pos == 0xFFFFFFFF) && (actual_frame > 0))
{
s_frames_to_skip++;
actual_frame--;
proposed_pos = g_frame_position[actual_frame];
}
skipped_I++;
// if we are only 2 frames away from an I frame, we will get a corrupted image and need to go back to
// the I frame before this one
if ((skipped_I < 2) && (s_frames_to_skip < 3) && (actual_frame > 0))
{
proposed_pos = 0xFFFFFFFF;
}
else
{
#ifdef VLDP_DEBUG
// printf("We've decided on a position within the file.\n");
// printf("skipped_I is %d\n", skipped_I);
// printf("s_frames_to_skip is %d\n", s_frames_to_skip);
// printf("actual_frame is %d\n", actual_frame);
#endif
break;
}
}
#ifdef VLDP_DEBUG
printf("frames_to_skip is %d, skipped_I is %d\n", s_frames_to_skip, skipped_I);
printf("position in mpeg2 stream we are seeking to : %x\n", proposed_pos);
#endif
io_seek(proposed_pos);
// fseek(g_mpeg_handle, proposed_pos, SEEK_SET); // go to the place in the stream where the I frame begins
// if we're seeking, we can change the frame right now ...
if (!skip)
{
g_out_info.current_frame = req_frame; // this is no longer incremented in null_draw_frame due to s_paused being set
s_uPendingSkipFrame = 0;
}
// if we're skipping, we have to leave the current frame alone until it changes, in order
// to be consistent with actual laserdisc behavior.
else
{
s_uPendingSkipFrame = req_frame;
}
s_blanked = 0; // we want to see the frame
ivldp_render();
} // end if the bounds check passed
else
{
fprintf(stderr, "SEARCH ERROR : frame %u was requested, but it is out of bounds\n", req_frame);
g_out_info.status = STAT_ERROR;
}
}
// parses an mpeg video stream to get its frame offsets, or if the parsing had taken place earlier
VLDP_BOOL ivldp_get_mpeg_frame_offsets(char *mpeg_name)
{
char datafilename[320] = { 0 };
VLDP_BOOL mpeg_datafile_good = VLDP_FALSE;
FILE *data_file = NULL;
VLDP_BOOL result = VLDP_TRUE;
unsigned int mpeg_size = 0;
struct dat_header header;
// GET LENGTH OF ACTUAL FILE
mpeg_size = io_length();
// change extension of file to be dat instead of (presumably) m2v
SAFE_STRCPY(datafilename, mpeg_name, sizeof(datafilename));
strcpy(&datafilename[strlen(mpeg_name)-3], "dat");
// loop until we get a good datafile
// or until we get an error
while (!mpeg_datafile_good && result)
{
data_file = fopen(datafilename, "rb"); // check to see if datafile exists
// If file cannot be opened, try to create it.
// Most likely the file cannot be opened because it doesn't
// exist.
if (!data_file)
{
result = ivldp_parse_mpeg_frame_offsets(datafilename, mpeg_size);
// we could open the file here, but there is no need to
// because we will loop back through and open the file anyway
}
// else if the file has been opened
else
{
// now that file exists and we have it open, we have to read it
#ifdef LARGE_FILE_SUPPORT // RDG
#ifdef WIN32
_fseeki64(data_file, 0L, SEEK_SET);
#else
fseeko64(data_file, 0L, SEEK_SET);
#endif
#else
fseek(data_file, 0L, SEEK_SET);
#endif
fread(&header, sizeof(header), 1, data_file); // read .DAT file header
// if version, file size, or finished are wrong, the dat file is no good and has to be regenerated
if ((header.length != mpeg_size) || (header.version != DAT_VERSION) || (header.finished != 1))
{
// printf("*** Alleged mpeg size is %u, actual size is %u\n", header.length, mpeg_size);
// printf("Finished flag is %x\n", header.finished);
// printf("DAT version is %x\n", header.version);
printf("NOTICE : MPEG data file has to be created again!\n");
fclose(data_file);
data_file = NULL;
// try to delete obsolete .DAT file so we can create a modern one
if (unlink(datafilename) == -1)
{
fprintf(stderr, "Couldn't delete obsolete .DAT file!\n");
result = VLDP_FALSE;
}
}
else
{
g_out_info.uses_fields = header.uses_fields;
mpeg_datafile_good = 1; // escape the loop
}
} // end if mpeg parsing was not necessary
} // end while we don't have a good datafile and haven't gotten an error
// if we didn't exit the loop because of an error, then we need to read the datafile
if (result && data_file)
{
g_totalframes = 0;
#ifdef VLDP_DEBUG
// unlink("frame_report.txt");
#endif
// read all the frame positions
// if we don't read 4 bytes, it means we've hit the EOF and we're done
while (fread(&g_frame_position[g_totalframes], 4, 1, data_file) == 1)
{
#ifdef VLDP_DEBUG
// FILE *tmp_F = fopen("frame_report.txt", "ab");
// fprintf(tmp_F, "Frame %d has offset of %x\n", g_totalframes, g_frame_position[g_totalframes]);
// fclose(tmp_F);
#endif
g_totalframes++;
// safety check, it is possible to make mpegs with too many frames to fit onto one CAV laserdisc
// (in fact I did this, and it caused a lot of problems in the debug stages hehe)
if (g_totalframes >= MAX_LDP_FRAMES)
{
fprintf(stderr, "ERROR : current mpeg has too many frames, VLDP will ignore any frames above %u\n", MAX_LDP_FRAMES);
break;
}
}
#ifdef VLDP_DEBUG
printf("*** g_totalframes is %u\n", g_totalframes);
printf("And frame 0's offset is %x\n", g_frame_position[0]);
#endif
}
// close any files that are still open
if (data_file)
{
fclose(data_file);
}
return result;
}
VLDP_BOOL ivldp_parse_mpeg_frame_offsets(char *datafilename, Uint32 mpeg_size)
{
int result = VLDP_TRUE;
FILE *data_file = fopen(datafilename, "wb"); // create file
struct dat_header header; // header to put inside .DAT file
// if we could create the file successfully, then we need to populate it
if (data_file)
{
Uint32 pos = 0; // position in the file
int count = 0;
int parse_result = 0;
header.version = DAT_VERSION;
header.finished = 0;
header.uses_fields = 0;
header.length = mpeg_size;
fwrite(&header, sizeof(header), 1, data_file);
// first thing that goes in the file is the .DAT header
// That way we can re-use the file another time with confidence that it's
// the right one
init_mpegscan();
g_in_info->report_parse_progress(-1); // notify other thread that we're starting
// keep reading the file while there is a file left to be read
do
{
#define PARSE_CHUNK 200000
parse_result = parse_video_stream(data_file, PARSE_CHUNK);
pos += PARSE_CHUNK;
// we want to give the user updates but don't want to flood them
if (count > 10)
{
count = 0;
// report progress to parent thread
g_in_info->report_parse_progress((double) pos / mpeg_size);
}
count++;
} while (parse_result == P_IN_PROGRESS);
g_in_info->report_parse_progress(1); // notify other thread that we're done
// if parse finished, then we have to update the header
if (parse_result != P_ERROR)
{
header.finished = 1;
header.uses_fields = 0;
if (parse_result == P_FINISHED_FIELDS)
{
header.uses_fields = 1;
}
#ifdef LARGE_FILE_SUPPORT // RDG
#ifdef WIN32
_fseeki64(data_file, 0L, SEEK_SET);
#else
fseeko64(data_file, 0L, SEEK_SET);
#endif
#else
fseek(data_file, 0L, SEEK_SET);
#endif
fwrite(&header, sizeof(header), 1, data_file); // save changes
}
// we have to close data file because it's write-only
// and it needs to be re-opened read-only
fclose(data_file);
data_file = NULL;
// if the mpeg did not finish parsing gracefully, we've got problems
// NOTE : I separated this from the other if above to guarantee that the file gets closed
if (parse_result == P_ERROR)
{
fprintf(stderr, "There was an error parsing the MPEG file.\n");
fprintf(stderr, "Either there is a bug in the parser or the MPEG file is corrupt.\n");
fprintf(stderr, "OR the user aborted the decoding process :)\n");
result = VLDP_FALSE;
unlink(datafilename);
}
} // end if we could create the file successfully
// we couldn't create data file which means no write permission probably
// this is probably a good time to shut VLDP down =]
else
{
fprintf(stderr, "Could not create file %s\n", datafilename);
fprintf(stderr, "This probably means you don't have permission to create the file\n");
result = VLDP_FALSE;
}
return result;
}
VLDP_BOOL io_open(const char *cpszFilename)
{
VLDP_BOOL bResult = VLDP_FALSE;
// make sure everything is closed
if ((!s_bPreCacheEnabled) && (!g_mpeg_handle))
{
g_mpeg_handle = fopen(cpszFilename, "rb");
if (g_mpeg_handle) bResult = VLDP_TRUE;
}
return bResult;
}
VLDP_BOOL io_open_precached(unsigned int uIdx)
{
VLDP_BOOL bResult = VLDP_FALSE;
// make sure everything is closed
if ((!g_mpeg_handle) && (!s_bPreCacheEnabled))
{
// make sure index is within range ...
if (uIdx < s_uPreCacheIdxCount)
{
bResult = VLDP_TRUE;
s_uCurPreCacheIdx = uIdx;
s_bPreCacheEnabled = VLDP_TRUE;
s_sPreCacheEntries[s_uCurPreCacheIdx].uPos = 0; // when opening, rewind to beginning
}
// else out of range ...
}
return bResult;
}
unsigned int io_read(void *buf, unsigned int uBytesToRead)
{
unsigned int uBytesRead = 0;
// if we're reading from a file stream
if (g_mpeg_handle)
{
uBytesRead = (unsigned int) fread(buf, 1, uBytesToRead, g_mpeg_handle);
}
// else we're reading from a precache stream
else
{
struct precache_entry_s *entry = &s_sPreCacheEntries[s_uCurPreCacheIdx];
unsigned int uBytesLeft = entry->uLength - entry->uPos;
// if we're trying to read beyond our means ...
if (uBytesToRead > uBytesLeft)
{
uBytesToRead = uBytesLeft;
}
memcpy(buf, ((unsigned char *) entry->ptrBuf) + entry->uPos, uBytesToRead);
uBytesRead = uBytesToRead;
entry->uPos += uBytesRead;
}
return uBytesRead;
}
VLDP_BOOL io_seek(unsigned int uPos)
{
VLDP_BOOL bResult = VLDP_FALSE;
if (g_mpeg_handle)
{
// BY RDG
// Preliminary support for MPEGS over 2GBs in size.
#ifdef LARGE_FILE_SUPPORT
#ifdef WIN32
__int64 uPos64 = uPos;
if (_fseeki64(g_mpeg_handle, uPos64, SEEK_SET) == 0)
{
bResult = VLDP_TRUE;
}
#else
off_t uPos64 = uPos;
if (fseeko64(g_mpeg_handle, uPos64, SEEK_SET) == 0)
{
bResult = VLDP_TRUE;
}
#endif
#else
if (fseek(g_mpeg_handle, uPos, SEEK_SET) == 0)
{
bResult = VLDP_TRUE;
}
#endif
}
else
{
struct precache_entry_s *entry = &s_sPreCacheEntries[s_uCurPreCacheIdx];
// if we're seeking within bounds ...
if (uPos < entry->uLength)
{
entry->uPos = uPos;
bResult = VLDP_TRUE;
}
}
return bResult;
}
void io_close()
{
if (g_mpeg_handle)
{
fclose(g_mpeg_handle);
g_mpeg_handle = NULL;
}
else if (s_bPreCacheEnabled)
{
s_bPreCacheEnabled = VLDP_FALSE;
}
// else nothing is open ...
}
VLDP_BOOL io_is_open()
{
VLDP_BOOL bResult = VLDP_FALSE;
if ((g_mpeg_handle) || (s_bPreCacheEnabled))
{
bResult = VLDP_TRUE;
}
return bResult;
}
unsigned int io_length()
{
unsigned int uResult = 0;
if (g_mpeg_handle)
{
struct stat the_stat;
fstat(fileno(g_mpeg_handle), &the_stat);
uResult = the_stat.st_size;
}
else if (s_bPreCacheEnabled)
{
uResult = s_sPreCacheEntries[s_uCurPreCacheIdx].uLength;
}
return uResult;
}