/* * 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 #include // for malloc/free #include #include #include //#include //#include "inttypesreplace.h" #include #include "vldp_internal.h" #include "vldp_common.h" #include "mpegscan.h" #ifdef WIN32 #include "../vc++/inttypes.h" #else #include #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 // 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; }