/* * ldp-vldp.cpp * * Copyright (C) 2001 Matt Ownby * * This file is part of DAPHNE, a laserdisc arcade game emulator * * DAPHNE 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. * * DAPHNE 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 */ // ldp-vldp.c // by Matt Ownby // pretends to be an LDP, but uses the VLDP library for output // (for people who have no laserdisc player) #ifdef WIN32 #pragma warning (disable:4100) // disable the warning about unreferenced formal parameters (MSVC++) #pragma warning (disable:4786) // disable warning about truncating to 255 in debug info #define strcasecmp stricmp #endif #ifdef DEBUG #include #endif // #include #include #include #include "../io/conout.h" #include "../io/error.h" #include "../video/video.h" #include "../timer/timer.h" #include "../daphne.h" // for get_quitflag, set_quitflag #include "../io/homedir.h" #include "../io/input.h" #include "../io/fileparse.h" #include "../io/mpo_mem.h" #include "../io/numstr.h" // for debug #include "../io/network.h" // to query amount of RAM the system has (get_sys_mem) #include "../game/game.h" #include "../video/rgb2yuv.h" #include "ldp-vldp.h" #include "framemod.h" #include "ldp-vldp-gl.h" // for OpenGL callbacks //#include "ldp-vldp-gp2x.h" // for GP2X callbacks (if no gp2x, this is harmless) #include "../vldp2/vldp/vldp.h" // to get the vldp structs #include "../video/palette.h" #include "../video/SDL_DrawText.h" #include "../video/blend.h" #define API_VERSION 11 static const unsigned int FREQ1000 = AUDIO_FREQ * 1000; // let compiler compute this ... // video overlay stuff Sint32 g_vertical_offset = 0; // (used a lot, we only want to calc it once) double g_dPercentComplete01 = 0.0; // send by child thread to indicate how far our parse is complete bool g_bGotParseUpdate = false; // if true, it means we've received a parse update from VLDP bool g_take_screenshot = false; // if true, a screenshot will be taken at the next available opportunity unsigned int g_vertical_stretch = 0; unsigned int g_filter_type = FILTER_NONE; // what type of filter to use on our data (if any) // these are globals because they are used by our callback functions SDL_Rect *g_screen_clip_rect = NULL; // used a lot, we only want to calculate once SDL_Overlay *g_hw_overlay = NULL; struct yuv_buf g_blank_yuv_buf; // this will contain a blank YUV overlay suitable for search/seek blanking Uint8 *g_line_buf = NULL; // temp sys RAM for doing calculations so we can do fastest copies to slow video RAM Uint8 *g_line_buf2 = NULL; // 2nd buf Uint8 *g_line_buf3 = NULL; // 3rd buf //////////////////////////////////////// // 2 pixels of black in YUY2 format (different for big and little endian) #if SDL_BYTEORDER == SDL_LIL_ENDIAN #define YUY2_BLACK 0x7f007f00 #else #define YUY2_BLACK 0x007f007f #endif ///////////////////////////////////////// // We have to dynamically load the .DLL/.so file due to incompatibilities between MSVC++ and mingw32 library files // These pointers and typedefs assist us in doing so. typedef const struct vldp_out_info *(*initproc)(const struct vldp_in_info *in_info); initproc pvldp_init; // pointer to the init proc ... // pointer to all functions the VLDP exposes to us ... const struct vldp_out_info *g_vldp_info = NULL; // info that we provide to the VLDP DLL struct vldp_in_info g_local_info; ///////////////////////////////////////// ldp_vldp::ldp_vldp() { m_bIsVLDP = true; // this is VLDP, so this value is true ... blitting_allowed = true; // blitting is allowed until we get to a certain point in initialization m_target_mpegframe = 0; // mpeg frame # we are seeking to m_mpeg_path = ""; m_cur_mpeg_filename = ""; m_file_index = 0; // # of mpeg files in our list m_framefile = ".txt"; m_framefile = g_game->get_shortgamename() + m_framefile; // create a sensible default framefile name m_bFramefileSet = false; m_altaudio_suffix = ""; // no alternate audio by default m_audio_file_opened = false; m_cur_ldframe_offset = 0; m_blank_on_searches = false; m_blank_on_skips = false; m_seek_frames_per_ms = 0; m_min_seek_delay = 0; m_vertical_stretch = 0; m_testing = false; // don't run tests by default m_bPreCache = m_bPreCacheForce = false; m_mPreCachedFiles.clear(); m_uSoundChipID = 0; enable_audio1(); // both channels should be on by default enable_audio2(); } ldp_vldp::~ldp_vldp() { pre_shutdown(); } // called when daphne starts up bool ldp_vldp::init_player() { bool result = false; bool need_to_parse = false; // whether we need to parse all video g_vertical_stretch = m_vertical_stretch; // callbacks don't have access to m_vertical_stretch // load the .DLL first in case we call any of its functions elsewhere if (load_vldp_lib()) { // try to read in the framefile if (read_frame_conversions()) { // just a sanity check to make sure their frame file is correct if (first_video_file_exists()) { // if the last video file has not been parsed, assume none of them have been // This is safe because if they have been parsed, it will just skip them if (!last_video_file_parsed()) { printnotice("Press any key to parse your video file(s). This may take a while. Press ESC if you'd rather quit."); need_to_parse = true; } if (audio_init() && !get_quitflag()) { // if our game is using video overlay, // AND if we're not doing tests that an overlay would interfere with // we'll use our slower callback if (g_game->get_active_video_overlay() && !m_testing) { g_local_info.prepare_frame = prepare_frame_callback_with_overlay; } // otherwise we can draw the frame much faster w/o worrying about // video overlay else { g_local_info.prepare_frame = prepare_frame_callback_without_overlay; } g_local_info.display_frame = display_frame_callback; #ifdef GP2X // gp2x receives a yuy2 frame directly g_local_info.prepare_yuy2_frame = prepare_gp2x_frame_callback; g_local_info.display_yuy2_frame = display_gp2x_frame_callback; #endif // GP2X g_local_info.report_parse_progress = report_parse_progress_callback; g_local_info.report_mpeg_dimensions = report_mpeg_dimensions_callback; g_local_info.render_blank_frame = blank_overlay; g_local_info.blank_during_searches = m_blank_on_searches; g_local_info.blank_during_skips = m_blank_on_skips; g_local_info.GetTicksFunc = GetTicksFunc; #ifdef USE_OPENGL // if we're using openGL, then we have a different set of callbacks ... if (get_use_opengl()) { g_local_info.prepare_frame = prepare_frame_GL_callback; g_local_info.display_frame = display_frame_GL_callback; g_local_info.report_mpeg_dimensions = report_mpeg_dimensions_GL_callback; g_local_info.render_blank_frame = render_blank_frame_GL_callback; if (!init_vldp_opengl()) { printerror("OpenGL v2.0 initialization failed."); set_quitflag(); } } #endif g_vldp_info = pvldp_init(&g_local_info); // if we successfully made contact with VLDP ... if (g_vldp_info != NULL) { // make sure we are using the API that we expect if (g_vldp_info->uApiVersion == API_VERSION) { // this number is used repeatedly, so we calculate it once g_vertical_offset = g_game->get_video_row_offset(); // if testing has been requested then run them ... if (m_testing) { list lstrPassed, lstrFailed; run_tests(lstrPassed, lstrFailed); // run releasetest to see actual results now ... printline("Run releasetest to see printed results!"); set_quitflag(); } // bPreCacheOK will be true if precaching succeeds or is never attempted bool bPreCacheOK = true; // If precaching has been requested, do it now. // The check for RAM requirements is done inside the // precache_all_video function, so we don't need to worry about that here. if (m_bPreCache) { bPreCacheOK = precache_all_video(); } // if we need to parse all the video if (need_to_parse) { parse_all_video(); } // if precaching succeeded or we didn't request precaching if (bPreCacheOK) { blitting_allowed = false; // this is the point where blitting isn't allowed anymore // open first file so that // we can draw video overlay even if the disc is not playing if (open_and_block(m_mpeginfo[0].name)) { // although we just opened a video file, we have not opened an audio file, // so we want to force a re-open of the same video file when we do a real search, // in order to ensure that the audio file is opened also. m_cur_mpeg_filename = ""; // set MPEG size ASAP in case different from NTSC default m_discvideo_width = g_vldp_info->w; m_discvideo_height = g_vldp_info->h; if (is_sound_enabled()) { struct sounddef soundchip; soundchip.type = SOUNDCHIP_VLDP; // no further parameters necessary m_uSoundChipID = add_soundchip(&soundchip); } result = true; } else { printline("LDP-VLDP ERROR : first video file could not be opened!"); } } // end if it's ok to proceed // else precaching failed else { printerror("LDP-VLDP ERROR : precaching failed"); } } // end if API matches up else { printerror("VLDP library is the wrong version!"); } } // end if reading the frame conversion file worked else { printline("LDP-VLDP ERROR : vldp_init returned NULL (which shouldn't ever happen)"); } } // if audio init succeeded else { // only report an audio problem if there is one if (!get_quitflag()) { printline("Could not initialize VLDP audio!"); } // otherwise report that they quit else { printline("VLDP : Quit requested, shutting down!"); } } // end if audio init failed or if user opted to quit instead of parse } // end if first file was present (sanity check) // else if first file was not found, we do just quit because an error is printed elsewhere } // end if framefile was read in properly else { // if the user didn't specify a framefile from the command-line, then give them a little hint. if (!m_bFramefileSet) { printline("You must specify a -framefile argument when using VLDP."); } // else the other error messages are more than sufficient } } // end if .DLL was loaded properly else { printline("Could not load VLDP dynamic library!!!"); } // if init didn't completely finish, then we need to shutdown everything if (!result) { shutdown_player(); } return result; } void ldp_vldp::shutdown_player() { // if VLDP has been loaded if (g_vldp_info) { g_vldp_info->shutdown(); g_vldp_info = NULL; } if (is_sound_enabled()) { if (!delete_soundchip(m_uSoundChipID)) { printline("ldp_vldp::shutdown_player WARNING : sound chip could not be deleted"); } } free_vldp_lib(); audio_shutdown(); free_yuv_overlay(); // de-allocate overlay if we have one allocated ... #ifdef USE_OPENGL free_gl_resources(); #endif } bool ldp_vldp::open_and_block(const string &strFilename) { bool bResult = false; // during parsing, blitting is allowed // NOTE: we want this here so that it is true before the initial open command is called. // Otherwise we'd put it inside wait_for_status ... blitting_allowed = true; // see if this filename has been precached map::const_iterator mi = m_mPreCachedFiles.find(strFilename); // if the file has not been precached and we are able to open it if ((mi == m_mPreCachedFiles.end() && (g_vldp_info->open((m_mpeg_path + strFilename).c_str()))) // OR if the file has been precached and we are able to refer to it || (g_vldp_info->open_precached(mi->second, (m_mpeg_path + strFilename).c_str()))) { bResult = wait_for_status(STAT_STOPPED); if (bResult) { m_cur_mpeg_filename = strFilename; } } blitting_allowed = false; return bResult; } bool ldp_vldp::precache_and_block(const string &strFilename) { bool bResult = false; // during precaching, blitting is allowed // NOTE: we want this here so that it is true before the initial open command is called. // Otherwise we'd put it inside wait_for_status ... blitting_allowed = true; if (g_vldp_info->precache((m_mpeg_path + strFilename).c_str())) { bResult = wait_for_status(STAT_STOPPED); } blitting_allowed = false; return bResult; } bool ldp_vldp::wait_for_status(unsigned int uStatus) { bool bResult = false; while (g_vldp_info->status == STAT_BUSY) { // if we got a parse update, then show it ... if (g_bGotParseUpdate) { // redraw screen blitter before we display it update_parse_meter(); vid_blank(); vid_blit(get_screen_blitter(), 0, 0); vid_flip(); g_bGotParseUpdate = false; } SDL_check_input(); // so that windows events are handled make_delay(20); // be nice to CPU } // if opening succeeded if ((unsigned int) g_vldp_info->status == uStatus) { bResult = true; } return bResult; } bool ldp_vldp::nonblocking_search(char *frame) { bool result = false; string filename = ""; string oggname = ""; Uint32 target_ld_frame = (Uint32) atoi(frame); Uint64 u64AudioTargetPos = 0; // position in audio to seek to (in samples) unsigned int seek_delay_ms = 0; // how many ms this seek must be delayed (to simulate laserdisc lag) audio_pause(); // pause the audio before we seek so we don't have overrun // do we need to compute seek_delay_ms? // (This is best done sooner than later so get_current_frame() is more accurate if (m_seek_frames_per_ms > 0) { // this value should be approximately the last frame we displayed // it doesn't get changed to the new frame until the seek is complete Uint32 cur_frame = get_current_frame(); unsigned int frame_delta = 0; // how many frames we're skipping // if we're seeking forward if (target_ld_frame > cur_frame) { frame_delta = target_ld_frame - cur_frame; } else { frame_delta = cur_frame - target_ld_frame; } seek_delay_ms = (unsigned int) (frame_delta / m_seek_frames_per_ms); #ifdef DEBUG string msg = "frame_delta is "; msg += numstr::ToStr(frame_delta); msg += " and seek_delay_ms is "; msg += numstr::ToStr(seek_delay_ms); printline(msg.c_str()); #endif } // make sure our seek delay does not fall below our minimum if (seek_delay_ms < m_min_seek_delay) seek_delay_ms = m_min_seek_delay; m_target_mpegframe = mpeg_info(filename, target_ld_frame); // try to get a filename // if we can convert target frame into a filename, do it! if (filename != "") { result = true; // now we assume success unless we fail // if the file to be opened is different from the one we have opened // OR if we don't yet have a file open ... // THEN open the file! :) if (filename != m_cur_mpeg_filename) { // if we were able to successfully open the video file if (open_and_block(filename)) { result = true; // this is done inside open_and_block now ... //m_cur_mpeg_filename = filename; // make video file our new current file // if sound is enabled, try to open an audio stream to match the video stream if (is_sound_enabled()) { // try to open an optional audio file to go along with video oggize_path(oggname, filename); m_audio_file_opened = open_audio_stream(oggname.c_str()); } } else { outstr("LDP-VLDP.CPP : Could not open video file "); printline(filename.c_str()); result = false; } } // if we're ok so far, try the search if (result) { // IMPORTANT : 'uFPKS' _must_ be queried AFTER a new mpeg has been opened, // because sometimes a framefile can include mpegs that have different framerates // from each other. unsigned int uFPKS = g_vldp_info->uFpks; m_discvideo_width = g_vldp_info->w; m_discvideo_height = g_vldp_info->h; // IMPORTANT : this must come before the optional FPS adjustment takes place!!! u64AudioTargetPos = get_audio_sample_position(m_target_mpegframe); if (!need_frame_conversion()) { // If the mpeg's FPS and the disc's FPS differ, we need to adjust the mpeg frame // NOTE: AVOID this if you can because it makes seeking less accurate if (g_game->get_disc_fpks() != uFPKS) { #ifndef GP2X // we want to show a msg w/ floating point stuff in it, which is too much for gp2x to handle string s = "NOTE: converting FPKS from " + numstr::ToStr(g_game->get_disc_fpks()) + " to " + numstr::ToStr(uFPKS) + ". This may be less accurate."; printline(s.c_str()); #endif m_target_mpegframe = (m_target_mpegframe * uFPKS) / g_game->get_disc_fpks(); } } // try to search to the requested frame if (g_vldp_info->search((Uint32) m_target_mpegframe, seek_delay_ms)) { // if we have an audio file opened, do an audio seek also if (m_audio_file_opened) { result = seek_audio(u64AudioTargetPos); } } else { printline("LDP-VLDP.CPP : Search failed in video file"); } } // else opening the file failed } // else mpeg_info() wasn't able to provide a filename ... else { printline("LDP-VLDP.CPP ERROR: frame could not be converted to file, probably due to a framefile error."); outstr("Your framefile must begin no later than frame "); printline(frame); printline("This most likely is your problem!"); } return(result); } // it should be safe to assume that if this function is getting called, that we have not yet got a result from the search int ldp_vldp::get_search_result() { int result = SEARCH_BUSY; // default to no change // if search is finished and has succeeded if (g_vldp_info->status == STAT_PAUSED) { result = SEARCH_SUCCESS; } // if the search failed else if (g_vldp_info->status == STAT_ERROR) { result = SEARCH_FAIL; } // else it's busy so we just wait ... return result; } unsigned int ldp_vldp::play() { unsigned int result = 0; string ogg_path = ""; bool bOK = true; // whether it's ok to issue the play command // if we haven't opened any mpeg file yet, then do so now if (m_cur_mpeg_filename == "") { bOK = open_and_block(m_mpeginfo[0].name); // get the first mpeg available in our list if (bOK) { // this is done inside open_and_block now ... //m_cur_mpeg_filename = m_mpeginfo[0].name; // if sound is enabled, try to load an audio stream to go with video stream ... if (is_sound_enabled()) { // try to open an optional audio file to go along with video oggize_path(ogg_path, m_mpeginfo[0].name); m_audio_file_opened = open_audio_stream(ogg_path.c_str()); } } else { outstr("LDP-VLDP.CPP : in play() function, could not open mpeg file "); printline(m_mpeginfo[0].name.c_str()); } } // end if we haven't opened a file yet // we need to keep this separate in case an mpeg is already opened if (bOK) { #ifdef DEBUG // we always expect this to be true, because we've just played :) assert(m_uElapsedMsSincePlay == 0); #endif audio_play(0); if (g_vldp_info->play(0)) { result = GET_TICKS(); } } if (!result) { printline("VLDP ERROR : play command failed!"); } return result; } // skips forward a certain # of frames during playback without pausing // Caveats: Does not work with an mpeg of the wrong framerate, does not work with an mpeg // that uses fields, and does not skip across file boundaries. // Returns true if skip was successful bool ldp_vldp::skip_forward(Uint32 frames_to_skip, Uint32 target_frame) { bool result = false; target_frame = (Uint32) (target_frame - m_cur_ldframe_offset); // take offset into account // this is ok (and possible) because we don't support skipping across files unsigned int uFPKS = g_vldp_info->uFpks; unsigned int uDiscFPKS = g_game->get_disc_fpks(); // We don't support skipping on mpegs that differ from the disc framerate if (uDiscFPKS == uFPKS) { // make sure they're not using fields // UPDATE : I don't see any reason why using fields would be a problem anymore, // but since I am not aware of any mpegs that use fields that require skipping, // I am leaving this restriction in here just to be safe. if (!g_vldp_info->uses_fields) { // advantage to this method is no matter how many times we skip, we won't drift because we are using m_play_time as our base // if we have an audio file opened if (m_audio_file_opened) { //Uint64 u64AudioTargetPos = (((Uint64) target_frame) * FREQ1000) / uDiscFPKS; Uint64 u64AudioTargetPos = get_audio_sample_position(target_frame); // seek and play if seeking was successful if (seek_audio(u64AudioTargetPos)) { audio_play(m_uElapsedMsSincePlay); } } // else we have no audio file open, but that's ok ... // if VLDP was able to skip successfully if (g_vldp_info->skip(target_frame)) { result = true; } else { printline("LDP-VLDP ERROR : video skip failed"); } } else { printline("LDP-VLDP ERROR : Skipping not supported with mpegs that use fields (such as this one)"); } } else { string s = "LDP-VLDP ERROR : Skipping not supported when the mpeg's framerate differs from the disc's (" + numstr::ToStr(uFPKS / 1000.0) + " vs " + numstr::ToStr(uDiscFPKS / 1000.0) + ")"; printline(s.c_str()); } return result; } void ldp_vldp::pause() { #ifdef DEBUG string s = "ldp_vldp::pause() : g_vldp_info's current frame is " + numstr::ToStr(g_vldp_info->current_frame) + " (" + numstr::ToStr(m_cur_ldframe_offset + g_vldp_info->current_frame) + " adjusted)"; printline(s.c_str()); #endif g_vldp_info->pause(); audio_pause(); } bool ldp_vldp::change_speed(unsigned int uNumerator, unsigned int uDenominator) { bool bResult = false; // if we aren't doing 1X, then stop the audio (this can be enhanced later) if ((uNumerator != 1) || (uDenominator != 1)) { audio_pause(); } // else the audio should be played at the correct location else { string filename; // dummy, not used // calculate where our audio position should be unsigned int target_mpegframe = mpeg_info(filename, get_current_frame()); Uint64 u64AudioTargetPos = get_audio_sample_position(target_mpegframe); // try to get the audio playing again if (seek_audio(u64AudioTargetPos)) { audio_play(m_uElapsedMsSincePlay); } else { printline("WARNING : trying to seek audio after playing at 1X failed"); } } if (g_vldp_info->speedchange(m_uFramesToSkipPerFrame, m_uFramesToStallPerFrame)) { bResult = true; } return bResult; } void ldp_vldp::think() { #ifdef USE_OPENGL // IMPORTANT: this must come before we update VLDP's uMsTimer to ensure that OpenGL has a chance to draw any pending frames before // a new frame comes in. if (get_use_opengl()) { ldp_vldp_gl_think(m_uVblankCount); } #endif // VLDP relies on this number // (m_uBlockedMsSincePlay is only non-zero when we've used blocking seeking) g_local_info.uMsTimer = m_uElapsedMsSincePlay + m_uBlockedMsSincePlay; } #ifdef DEBUG // This function tests to make sure VLDP's current frame is the same as our internal current frame. unsigned int ldp_vldp::get_current_frame() { Sint32 result = 0; // safety check if (!g_vldp_info) return 0; unsigned int uFPKS = g_vldp_info->uFpks; // the # of frames that have advanced since our search unsigned int uOffset = g_vldp_info->current_frame - m_target_mpegframe; unsigned int uStartMpegFrame = m_target_mpegframe; // since the mpeg's beginning does not correspond to the laserdisc's beginning, we add the offset result = m_cur_ldframe_offset + uStartMpegFrame + uOffset; // if we're out of bounds, just set it to 0 if (result <= 0) { result = 0; } // if we got a legitimate frame else { // FIXME : THIS CODE HAS BUGS IN IT, I HAVEN'T TRACKED THEM DOWN YET HEHE // if the mpeg's FPS and the disc's FPS differ, we need to adjust the frame that we return if (g_game->get_disc_fpks() != uFPKS) { return m_uCurrentFrame; //result = (result * g_game->get_disc_fpks()) / uFPKS; } } // if there's even a slight mismatch, report it ... if ((unsigned int) result != m_uCurrentFrame) { // if VLDP is ahead, that is especially disturbing if ((unsigned int) result > m_uCurrentFrame) { string s = "ldp-vldp::get_current_frame() [vldp ahead]: internal frame is " + numstr::ToStr(m_uCurrentFrame) + ", vldp's current frame is " + numstr::ToStr(result); printline(s.c_str()); s = "g_local_info.uMsTimer is " + numstr::ToStr(g_local_info.uMsTimer) + ", which means frame offset " + numstr::ToStr((g_local_info.uMsTimer * uFPKS) / 1000000); #ifndef GP2X s += " (" + numstr::ToStr(g_local_info.uMsTimer * uFPKS * 0.000001) + ") "; #endif printline(s.c_str()); s = "m_uCurrentOffsetFrame is " + numstr::ToStr(m_uCurrentOffsetFrame) + ", m_last_seeked frame is " + numstr::ToStr(m_last_seeked_frame); printline(s.c_str()); unsigned int uMsCorrect = ((result - m_last_seeked_frame) * 1000000) / uFPKS; s = "correct elapsed ms is " + numstr::ToStr(uMsCorrect); #ifndef GP2X // show float if we have decent FPU s += " (" + numstr::ToStr((result - m_last_seeked_frame) * 1000000.0 / uFPKS) + ") "; #endif s += ", which is frame offset " + numstr::ToStr((uMsCorrect * uFPKS) / 1000000); #ifndef GP2X // show float result if we have a decent FPU s += " (" + numstr::ToStr(uMsCorrect * uFPKS * 0.000001) + ")"; #endif printline(s.c_str()); } // This will be behind more than 1 frame (legitimately) playing at faster than 1X, // so uncomment this with that in mind ... /* // else if VLDP is behind more than 1 frame, that is also disturbing else if ((m_uCurrentFrame - result) > 1) { string s = "ldp-vldp::get_current_frame() [vldp behind]: internal frame is " + numstr::ToStr(m_uCurrentFrame) + ", vldp's current frame is " + numstr::ToStr(result); printline(s.c_str()); } // else vldp is only behind 1 frame, that is to be expected at times due to threading issues ... */ } return m_uCurrentFrame; } #endif // takes a screenshot of the current frame + any video overlay void ldp_vldp::request_screenshot() { g_take_screenshot = true; } void ldp_vldp::set_search_blanking(bool enabled) { m_blank_on_searches = enabled; } void ldp_vldp::set_skip_blanking(bool enabled) { m_blank_on_skips = enabled; } void ldp_vldp::set_seek_frames_per_ms(double value) { m_seek_frames_per_ms = value; } void ldp_vldp::set_min_seek_delay(unsigned int value) { m_min_seek_delay = value; } // sets the name of the frame file void ldp_vldp::set_framefile(const char *filename) { m_bFramefileSet = true; m_framefile = filename; } // sets alternate soundtrack void ldp_vldp::set_altaudio(const char *audio_suffix) { m_altaudio_suffix = audio_suffix; } // sets alternate soundtrack void ldp_vldp::set_vertical_stretch(unsigned int value) { m_vertical_stretch = value; } void ldp_vldp::test_helper(unsigned uIterations) { // We aren't calling think_delay because we want to have a lot of milliseconds pass quickly without actually waiting. // make a certain # of milliseconds elapse .... for (unsigned int u = 0; u < uIterations; u++) { pre_think(); } } void ldp_vldp::run_tests(list &lstrPassed, list &lstrFailed) { // these tests just basically stress the VLDP .DLL to make sure it will never crash // under any circumstances string s = ""; bool result = false; // if we have at least 1 entry in our framefile if (m_file_index > 0) { string path = m_mpeginfo[0].name; // create full pathname to file // TEST #1 : try opening and playing without seeking s = "VLDP TEST #1 (playing and skipping)"; if (open_and_block(path) == 1) { g_local_info.uMsTimer = GET_TICKS(); if (g_vldp_info->play(g_local_info.uMsTimer) == 1) { test_helper(1000); if (g_vldp_info->skip(5) == 1) { lstrPassed.push_back(s); } else { lstrFailed.push_back(s + " (opened and played, but could not skip)"); } } else lstrFailed.push_back(s + " (opened but could not play)"); } else lstrFailed.push_back(s); // TEST #2 : try opening the file repeatedly // This should not cause any problems s = "VLDP TEST #2 (opening repeatedly)"; result = true; for (int i = 0; i < 10; i++) { if (!open_and_block(path)) { result = false; break; } } if (result) lstrPassed.push_back(s); else lstrFailed.push_back(s); // TEST #3 : try opening the file and seeking to an illegal frame s = "VLDP TEST #3 (seeking to illegal frame)"; if (!g_vldp_info->search_and_block(65534, 0)) { lstrPassed.push_back(s); } else lstrFailed.push_back(s); // TEST #4 : try seeking to a (hopefully) legitimate frame s = "VLDP TEST #4 (seeking to legit frame)"; if (g_vldp_info->search_and_block(50, 0) == 1) { lstrPassed.push_back(s); //test_helper(1000); } else { lstrFailed.push_back(s); } // TEST #5 : play to the end of the file, then try playing again to see what happens s = "VLDP TEST #5 (playing to end of file, then playing again)"; if (open_and_block(path) == 1) { g_local_info.uMsTimer = m_uElapsedMsSincePlay = m_uBlockedMsSincePlay = 0; if (g_vldp_info->play(g_local_info.uMsTimer) == 1) { // wait for file to end ... while ((g_vldp_info->status == STAT_PLAYING) && (!get_quitflag())) { SDL_check_input(); test_helper(250); } if (g_vldp_info->play(g_local_info.uMsTimer) == 1) { lstrPassed.push_back(s); test_helper(1000); } else { lstrFailed.push_back(s + " - the second time around"); } } else lstrFailed.push_back(s + "opened file, but failed to play it)"); } else lstrFailed.push_back(s); // TEST #6 : play to the end of the file, then try seeking to see what happens s = "VLDP TEST #6 (play to end of file, then seek)"; if (open_and_block(path) == 1) { g_local_info.uMsTimer = m_uElapsedMsSincePlay = m_uBlockedMsSincePlay = 0; if (g_vldp_info->play(g_local_info.uMsTimer) == 1) { // wait for file to end ... while ((g_vldp_info->status == STAT_PLAYING) && (!get_quitflag())) { SDL_check_input(); test_helper(250); } if (g_vldp_info->search_and_block(50, 0) == 1) { lstrPassed.push_back(s); test_helper(1000); } else { lstrFailed.push_back(s + "(failed the second time around)"); } } else lstrFailed.push_back(s + "(opened file, but failed to play it)"); } else lstrFailed.push_back(s); // TEST #7 : open, seek, play, all testing timing s = "VLDP TEST #7 (seeking, playing with timing tested)"; if (open_and_block(path) == 1) { g_local_info.uMsTimer = m_uElapsedMsSincePlay = m_uBlockedMsSincePlay = 0; // search to beginning frame ... if (g_vldp_info->search_and_block(0, 0) == 1) { test_helper(1); // make 1 ms elapse ... SDL_Delay(1); // give VLDP thread a chance to make its own updates ... // make sure that frame is what we expect it to be ... if (g_vldp_info->current_frame == 0) { test_helper(50); // make 50 ms elapse SDL_Delay(1); // give VLDP thread a chance to make updates // make sure current frame has not changed if (g_vldp_info->current_frame == 0) { g_local_info.uMsTimer = m_uElapsedMsSincePlay = m_uBlockedMsSincePlay = 0; // so now we start playing ... if (g_vldp_info->play(0) == 1) { test_helper(32); // pause 32 ms (right before frame should change) SDL_Delay(50); // vldp thread blah blah ... // current frame still should not have changed if (g_vldp_info->current_frame == 0) { test_helper(1); // 1 more ms, frame should change now SDL_Delay(50); // don't fail simply due to the cpu being overloaded if (g_vldp_info->current_frame == 1) { lstrPassed.push_back(s); } else lstrFailed.push_back(s + " (frame didn't change to 1)"); } else lstrFailed.push_back(s + " (frame changed to " + numstr::ToStr(g_vldp_info->current_frame) + " after play)"); } else lstrFailed.push_back(s + " (play failed)"); } else lstrFailed.push_back(s + " (current frame changed from 0 after seek)"); } else lstrFailed.push_back(s + " (opened, but current frame was not 0)"); } else lstrFailed.push_back(s + " (opened but could not search)"); } else lstrFailed.push_back(s); } // end if there was 1 file in the framefile else lstrFailed.push_back("VLDP TESTS (Framefile had no entries)"); } // handles VLDP-specific command line args bool ldp_vldp::handle_cmdline_arg(const char *arg) { bool result = true; if (strcasecmp(arg, "-blend")==0) { g_filter_type |= FILTER_BLEND; } else if (strcasecmp(arg, "-scanlines")==0) { g_filter_type |= FILTER_SCANLINES; } // should we run a few VLDP tests when the player is initialized? else if (strcasecmp(arg, "-vldptest")==0) { m_testing = true; } // should we precache all video streams to RAM? else if (strcasecmp(arg, "-precache")==0) { m_bPreCache = true; } // even if we don't have enough RAM, should we still precache all video streams to RAM? else if (strcasecmp(arg, "-precache_force")==0) { m_bPreCache = true; m_bPreCacheForce = true; } // else it's unknown else { result = false; } return result; } ////////////////////////////////// // loads the VLDP dynamic library, returning true on success bool ldp_vldp::load_vldp_lib() { bool result = false; #ifndef STATIC_VLDP // if we're loading VLDP dynamically #ifndef DEBUG m_dll_instance = M_LOAD_LIB(vldp2); // load VLDP2.DLL or libvldp2.so #else m_dll_instance = M_LOAD_LIB(vldp2_dbg); #endif // If the handle is valid, try to get the function address. if (m_dll_instance) { pvldp_init = (initproc) M_GET_SYM(m_dll_instance, "vldp_init"); // if init function was found if (pvldp_init) { result = true; } else { printerror("VLDP LOAD ERROR : vldp_init could not be loaded"); } } else { printerror("ERROR: could not open the VLDP2 dynamic library (file not found maybe?)"); } #else // else if we're loading VLDP statically pvldp_init = vldp_init; result = true; #endif // STATIC_VLDP return result; } // frees the VLDP dynamic library if we loaded it in void ldp_vldp::free_vldp_lib() { #ifndef STATIC_VLDP // if we loaded vldp dynamically, then we need to unload it properly... // don't free if the library was never opened if (m_dll_instance) { M_FREE_LIB(m_dll_instance); } #endif // STATIC_VLDP } // read frame conversions in from LD-frame to mpeg-frame data file bool ldp_vldp::read_frame_conversions() { struct mpo_io *p_ioFileConvert; string s = ""; string frame_string = ""; bool result = false; string framefile_path; framefile_path = m_framefile; p_ioFileConvert = mpo_open(framefile_path.c_str(), MPO_OPEN_READONLY); // if the file was not found in the relative directory, try looking for it in the framefile directory if (!p_ioFileConvert) { framefile_path = g_homedir.get_framefile(framefile_path); // add directory to front of path p_ioFileConvert = mpo_open(framefile_path.c_str(), MPO_OPEN_READONLY); } // if the framefile was opened successfully if (p_ioFileConvert) { MPO_BYTES_READ bytes_read = 0; char *ff_buf = (char *) MPO_MALLOC((unsigned int) (p_ioFileConvert->size+1)); // add an extra byte to null terminate if (ff_buf != NULL) { if (mpo_read(ff_buf, (unsigned int) p_ioFileConvert->size, &bytes_read, p_ioFileConvert)) { // if we successfully read in the whole framefile if (bytes_read == p_ioFileConvert->size) { string err_msg = ""; ff_buf[bytes_read] = 0; // NULL terminate the end of the file to be safe // if parse was successful if (parse_framefile( (const char *) ff_buf, framefile_path.c_str(), m_mpeg_path, &m_mpeginfo[0], m_file_index, sizeof(m_mpeginfo) / sizeof(struct fileframes), err_msg)) { outstr("Framefile parse succeeded. Video/Audio directory is: "); printline(m_mpeg_path.c_str()); result = true; } else { printerror("Framefile Parse Error"); printline(err_msg.c_str()); err_msg = "Mpeg Path : " + m_mpeg_path; printline(err_msg.c_str()); printline("---BEGIN FRAMEFILE CONTENTS---"); // print the entire contents of the framefile to make it easier to us to debug newbie problems using their daphne_log.txt printline(ff_buf); printline("---END FRAMEFILE CONTENTS---"); } } else printerror("ldp-vldp.cpp : framefile read error"); } else printerror("ldp-vldp.cpp : framefile read error"); } else printerror("ldp-vldp.cpp : mem alloc error"); mpo_close(p_ioFileConvert); } else { s = "Could not open framefile : " + m_framefile; printerror(s.c_str()); } return result; } // if file does not exist, we print an error message bool ldp_vldp::first_video_file_exists() { string full_path = ""; bool result = false; // if we have at least one file if (m_file_index) { full_path = m_mpeg_path; full_path += m_mpeginfo[0].name; if (mpo_file_exists(full_path.c_str())) { result = true; } else { full_path = "Could not open file : " + full_path; // keep using full_path just because it's convenient printerror(full_path.c_str()); } } else { printerror("ERROR : Framefile seems empty, it's probably invalid"); printline("Read the documentation to learn how to create framefiles."); } return result; } // returns true if the last video file has been parsed // This is so we don't parse_all_video if all files are already parsed bool ldp_vldp::last_video_file_parsed() { string full_path = ""; bool result = false; // if we have at least one file if (m_file_index > 0) { full_path = m_mpeg_path; full_path += m_mpeginfo[m_file_index-1].name; full_path.replace(full_path.length() - 3, 3, "dat"); // replace pre-existing suffix (which is probably .m2v) with 'dat' if (mpo_file_exists(full_path.c_str())) { result = true; } } // else there is a problem with the frame file so return false return result; } // opens (and closes) all video files, forcing any unparsed video files to get parsed void ldp_vldp::parse_all_video() { unsigned int i = 0; for (i = 0; i < m_file_index; i++) { // if the file can be opened... if (open_and_block(m_mpeginfo[i].name)) { g_vldp_info->search_and_block(0, 0); // search to frame 0 to render it so the user has something to watch while he/she waits think(); // let opengl have a chance to display the frame } else { outstr("LDP-VLDP: Could not parse video because file "); outstr(m_mpeginfo[i].name.c_str()); printline(" could not be opened."); break; } } } bool ldp_vldp::precache_all_video() { bool bResult = true; unsigned int i = 0; string full_path = ""; mpo_io *io = NULL; // to make sure the total file size is correct // (it's legal for a framefile to have the same file listed more than once) set sDupePreventer; MPO_UINT64 u64TotalBytes = 0; // first compute file size ... for (i = 0; i < m_file_index; i++) { full_path = m_mpeg_path + m_mpeginfo[i].name; // create full pathname to file // if this file's size hasn't already been taken into account if (sDupePreventer.find(m_mpeginfo[i].name) == sDupePreventer.end()) { io = mpo_open(full_path.c_str(), MPO_OPEN_READONLY); if (io) { u64TotalBytes += io->size; mpo_close(io); // we're done with this file ... sDupePreventer.insert(m_mpeginfo[i].name); // we've used it now ... } // else file can't be opened ... else { outstr("LDP-VLDP: when precaching, the file "); outstr(full_path.c_str()); printline(" cannot be opened."); bResult = false; break; } } // end if the filesize hasn't been taken into account // else the filesize has already been taken into account } // if we were able to compute the file size ... if (bResult) { const unsigned int uFUDGE = 256; // how many megs we assume the OS needs in addition to our application running unsigned int uReqMegs = (unsigned int) ((u64TotalBytes / 1048576) + uFUDGE); unsigned int uMegs = get_sys_mem(); // if we have enough memory (accounting for OS overhead, which may need to increase in the future) // OR if the user wants to force precaching despite our check ... if ((uReqMegs < uMegs) || (m_bPreCacheForce)) { for (i = 0; i < m_file_index; i++) { // if the file in question has not yet been precached if (m_mPreCachedFiles.find(m_mpeginfo[i].name) == m_mPreCachedFiles.end()) { // try to precache and if it fails, bail ... if (precache_and_block(m_mpeginfo[i].name)) { // store the index of the file that we last precached m_mPreCachedFiles[m_mpeginfo[i].name] = g_vldp_info->uLastCachedIndex; } else { full_path = m_mpeg_path + m_mpeginfo[i].name; outstr("LDP-VLDP: precaching of file "); outstr(full_path.c_str()); printline(" failed."); bResult = false; } } // end if file has not been precached // else file has already been precached, so don't precache it again } } else { printline( ((string) "Not enough memory to precache video stream. You have about " + numstr::ToStr(uMegs) + " but need " + numstr::ToStr(uReqMegs)).c_str()); bResult = false; } } return bResult; } Uint64 ldp_vldp::get_audio_sample_position(unsigned int uTargetMpegFrame) { Uint64 u64AudioTargetPos = 0; if (!need_frame_conversion()) { u64AudioTargetPos = (((Uint64) uTargetMpegFrame) * FREQ1000) / g_game->get_disc_fpks(); // # of samples to seek to in the audio stream } // If we are already doing a frame conversion elsewhere, we don't want to do it here again twice // but we do need to set the audio to the correct time else { u64AudioTargetPos = (((Uint64) uTargetMpegFrame) * FREQ1000) / get_frame_conversion_fpks(); } return u64AudioTargetPos; } // returns # of frames to seek into file, and mpeg filename // if there's an error, filename is "" // WARNING: This assumes the mpeg and disc are running at exactly the same FPS // If they aren't, you will need to calculate the actual mpeg frame to seek to // The reason I don't return time here instead of frames is because it is more accurate to // return frames if they are at the same FPS (which hopefully they are hehe) Uint32 ldp_vldp::mpeg_info (string &filename, Uint32 ld_frame) { unsigned int index = 0; Uint32 mpeg_frame = 0; // which mpeg frame to seek (assuming mpeg and disc have same FPS) filename = ""; // blank 'filename' means error, so we default to this condition for safety reasons // find the mpeg file that has the LD frame inside of it while ((index+1 < m_file_index) && (ld_frame >= m_mpeginfo[index+1].frame)) { index = index + 1; } // make sure that the frame they've requested comes after the first frame in our framefile if (ld_frame >= m_mpeginfo[index].frame) { // make sure a filename exists (when can this ever fail? verify!) if (m_mpeginfo[index].name != "") { filename = m_mpeginfo[index].name; mpeg_frame = (Uint32) (ld_frame - m_mpeginfo[index].frame); m_cur_ldframe_offset = m_mpeginfo[index].frame; } else { printline("VLDP error, no filename found"); mpeg_frame = 0; } } // else frame is out of range ... return(mpeg_frame); } //////////////////////////////////////////////////////////////////////////////////////// // puts the yuv_callback into a blocking state // This is necessary if the gamevid ever becomes invalid for a period of time (ie it gets free'd and re-allocated in seektest) // timeout is how many ms to wait before giving up // returns true if it got the lock or false if it couldn't get a lock bool ldp_vldp::lock_overlay(Uint32 timeout) { bool bRes = false; if (g_vldp_info) { bRes = g_vldp_info->lock(timeout) == VLDP_TRUE; } // else g_vldp_info is NULL which means the init function hasn't been called yet probably return bRes; } // releases the yuv_callback from its blocking state bool ldp_vldp::unlock_overlay(Uint32 timeout) { /* Uint32 time = refresh_ms_time(); mutex_lock_request = false; // sleep until the yuv callback acknowledges that it has control once again while (mutex_lock_acknowledge) { SDL_Delay(0); // if we've timed out if (elapsed_ms_time(time) > timeout) { printline("LDP_VLDP : unlock_overlay timed out while trying to unlock"); break; } } return (!mutex_lock_acknowledge); */ return (g_vldp_info->unlock(timeout) == VLDP_TRUE); } bool ldp_vldp::parse_framefile(const char *pszInBuf, const char *pszFramefileFullPath, string &sMpegPath, struct fileframes *pFrames, unsigned int &frame_idx, unsigned int max_frames, string &err_msg) { bool result = false; const char *pszPtr = pszInBuf; unsigned int line_number = 0; // for debugging purposes char ch = 0; frame_idx = 0; err_msg = ""; // read absolute or relative directory pszPtr = read_line(pszPtr, sMpegPath); // if there are no additional lines if (!pszPtr) { // if there is at least 1 line if (sMpegPath.size() > 0) { err_msg = "Framefile only has 1 line in it. Framefiles must have at least 2 lines in it."; } else err_msg = "Framefile appears to be empty. Framefile must have at least 2 lines in it."; return false; // normally I only like to have 1 return per function, but this is a good spot to return.. } ++line_number; // if we get this far, it means we've read our first line // If sMpegPath is NOT an absolute path (doesn't begin with a unix '/' or have the second char as a win32 ':') // then we want to use the framefile's path for convenience purposes // (this should be win32 and unix compatible) if ((sMpegPath[0] != '/') && (sMpegPath[0] != '\\') && (sMpegPath[1] != ':')) { string path = ""; // try to isolate the path of the framefile if (get_path_of_file(pszFramefileFullPath, path)) { // put the path of the framefile in front of the relative path of the mpeg files // This will allow people to move the location of their mpegs around to // different directories without changing the framefile at all. // For example, if the framefile and the mpegs are in the same directory, // then the framefile's first line could be "./" sMpegPath = path + sMpegPath; } } // else it is an absolute path, so we ignore the framefile's path string s = ""; // convert all \'s to /'s to be more unix friendly (doesn't hurt win32) for (unsigned int i = 0; i < sMpegPath.length(); i++) { ch = sMpegPath[i]; if (ch == '\\') ch = '/'; s += ch; } sMpegPath = s; // Clean up after the user if they didn't end the path with a '/' character if (ch != '/') { sMpegPath += "/"; // windows will accept / as well as \, so we're ok here } string word = "", remaining = ""; result = true; // from this point, we should assume success Sint32 frame = 0; // parse through entire file while (pszPtr != NULL) { pszPtr = read_line(pszPtr, s); // read in a line ++line_number; // if we can find the first word (frame #) if (find_word(s.c_str(), word, remaining)) { // check for overflow if (frame_idx >= max_frames) { err_msg = "Framefile has too many entries in it." " You can increase the value of MAX_MPEG_FILES and recompile."; result = false; break; } frame = numstr::ToInt32(word.c_str()); // this should work, even with whitespace after it // If frame is valid AND we are able to find the name of the // (a non-integer will be converted to 0, so we need to make sure it really is supposed to be 0) if (((frame != 0) || (word == "0")) && find_word(remaining.c_str(), word, remaining)) { pFrames[frame_idx].frame = (Sint32) frame; pFrames[frame_idx].name = word; ++frame_idx; } else { // This error has been stumping self-proclaimed "experienced emu'ers" // so I am going to extra effort to make it clear to them what the // problem is. err_msg = "Expected a number followed by a string, but on line " + numstr::ToStr(line_number) + ", found this: " + s + "("; // print hex values of bad string to make troubleshooting easier for (size_t idx = 0; idx < s.size(); idx++) { err_msg += "0x" + numstr::ToStr(s[idx], 16) + " "; } err_msg += ")"; result = false; break; } } // else it is probably just an empty line, so we can ignore it } // end while file hasn't been completely parsed // if we ended up with no entries AND didn't get any error, then the framefile is bad if ((frame_idx == 0) && (result == true)) { err_msg = "Framefile appears to not have any entries in it."; result = false; } return result; } ////////////////////////////////////////////////////////////////////// // returns VLDP_TRUE on success, VLDP_FALSE on failure int prepare_frame_callback_with_overlay(struct yuv_buf *src) { int result = VLDP_FALSE; /* // if another thread has requested that we block, then do so. // DANGER : This is dangerous because it has endless loop potential // However, it is also fast and therefore we just need to be conscious coders and not let endless loops happen if (mutex_lock_request) { mutex_lock_acknowledge = 1; // wait until other thread has unlocked (endless loop potential) while (mutex_lock_request) { SDL_Delay(0); } mutex_lock_acknowledge = 0; } */ if (SDL_LockYUVOverlay(g_hw_overlay) == 0) { SDL_Surface *gamevid = g_game->get_finished_video_overlay(); // This could change at any time (double buffering, for example) // so we are forced to query it every time we run this function. If someone has a better idea, let me know // sanity check. Make sure the game video is the proper width. if ((gamevid->w << 1) == g_hw_overlay->w) { // adjust for vertical offset // We use _half_ of the requested vertical offset because the mpeg video is twice // the size of the overlay Uint8 *gamevid_pixels = (Uint8 *) gamevid->pixels - (gamevid->w * (g_vertical_offset - g_vertical_stretch)); #ifdef DEBUG // make sure that the g_vertical_offset isn't out of bounds Uint8 *last_valid_byte = ((Uint8 *) gamevid->pixels) + (gamevid->w * gamevid->h) - 1; assert(gamevid_pixels < last_valid_byte); #endif unsigned int row = 0; unsigned int col = 0; Uint32 w_double = g_hw_overlay->w << 1; // twice the overlay width, to avoid calculating this more than once Uint32 h_half = g_hw_overlay->h >> 1; // half of the overlay height, to avoid calculating this more than once Uint8 *dst_ptr; // this could be global, any benefit? t_yuv_color* yuv_palette = get_yuv_palette(); unsigned int channel0_pitch = g_hw_overlay->pitches[0]; // this val gets used a lot so we put it into a var dst_ptr = (Uint8 *) g_hw_overlay->pixels[0]; Uint8 *Y = (Uint8 *) src->Y; Uint8 *Y2 = (Uint8 *) src->Y + g_hw_overlay->w; Uint8 *U = (Uint8 *) src->U; Uint8 *V = (Uint8 *) src->V; // if letterbox removal is active, shift video down to compensate for (unsigned int skip = 0; skip < g_vertical_stretch; skip += 2) { Y += (g_hw_overlay->w * 4); Y2 += (g_hw_overlay->w * 4); U += g_hw_overlay->w; V += g_hw_overlay->w; } // do 2 rows at a time for (row = 0; row < h_half; row++) { // calculate this here to avoid calculating too often int adjusted_row = ((int) row) - g_vertical_offset; bool row_in_range = ((adjusted_row >= 0) && (adjusted_row < gamevid->h)); t_yuv_color *palette = NULL; // do 4 bytes at a time, for twice the width of the overlay since we're using YUY2 for (col = 0; col < w_double; col += 4) { // if we can safely draw from the video overlay if (row_in_range) palette = &yuv_palette[*gamevid_pixels]; // If we are out of range, OR if the current color is transparent, // then draw the mpeg video pixel instead of the video overlay // (if palette is NULL, the compiler shouldn't try to dereference palette->transparent) if ((palette == NULL) || palette->transparent) { unsigned int Y_chunk = *((Uint16 *) Y); unsigned int Y2_chunk = *((Uint16 *) Y2); unsigned int V_chunk = *V; unsigned int U_chunk = *U; #if SDL_BYTEORDER == SDL_LIL_ENDIAN //Little-Endian (Intel) *((Uint32 *) (g_line_buf + col)) = (Y_chunk & 0xFF) | (U_chunk << 8) | ((Y_chunk & 0xFF00) << 8) | (V_chunk << 24); *((Uint32 *) (g_line_buf2 + col)) = (Y2_chunk & 0xFF) | (U_chunk << 8) | ((Y2_chunk & 0xFF00) << 8) | (V_chunk << 24); #else //Big-Endian (Mac) *((Uint32 *) (g_line_buf + col)) = ((Y_chunk & 0xFF00) << 16) | ((U_chunk) << 16) | ((Y_chunk & 0xFF) << 8) | (V_chunk); *((Uint32 *) (g_line_buf2 + col)) = ((Y2_chunk & 0xFF00) << 16) | ((U_chunk) << 16) | ((Y2_chunk & 0xFF) << 8) | (V_chunk); #endif } // if we have an overlay pixel to be drawn else { #if SDL_BYTEORDER == SDL_LIL_ENDIAN //Little-Endian (Intel) *((Uint32 *) (g_line_buf + col)) = *((Uint32 *) (g_line_buf2 + col)) = palette->y | (palette->u << 8) | (palette->y << 16) | (palette->v << 24); #else //Big-Endian (Mac) *((Uint32 *) (g_line_buf + col)) = *((Uint32 *) (g_line_buf2 + col)) = (palette->y << 24) | (palette->u << 16) | (palette->y << 8) | (palette->v); #endif } Y += 2; Y2 += 2; U++; V++; gamevid_pixels++; } // if we're not doing scanlines if (!(g_filter_type & FILTER_SCANLINES)) { // no filtering at all if (!(g_filter_type & FILTER_BLEND)) { memcpy(dst_ptr, g_line_buf, (g_hw_overlay->w << 1)); memcpy(dst_ptr + channel0_pitch, g_line_buf2, (g_hw_overlay->w << 1)); } else { g_blend_func(); // blend the two lines into g_line_buf3 // this won't affect video overlay because it is already doubled in size anyway memcpy(dst_ptr, g_line_buf3, (g_hw_overlay->w << 1)); memcpy(dst_ptr + channel0_pitch, g_line_buf3, (g_hw_overlay->w << 1)); } } // if we're doing scanlines else { // do a black YUY2 line (the first line should be black to workaround nvidia bug) for (int i = 0; i < (g_hw_overlay->w << 1); i+=4) { *((Uint32 *) (dst_ptr + i)) = YUY2_BLACK; // this value is black in YUY2 mode } if (g_filter_type & FILTER_BLEND) { g_blend_func(); // blend the two lines into g_line_buf3 // this won't affect video overlay because it is already doubled in size anyway memcpy(dst_ptr + channel0_pitch, g_line_buf3, (g_hw_overlay->w << 1)); } else { memcpy(dst_ptr + channel0_pitch, g_line_buf, (g_hw_overlay->w << 1)); // this could be g_line_buf2 also } } dst_ptr += (channel0_pitch << 1); // we've done 2 rows, so skip a row Y += g_hw_overlay->w; // we've done 2 vertical Y pixels, so skip a row Y2 += g_hw_overlay->w; } // if we've been instructed to take a screenshot, do so now that the overlay is in place if (g_take_screenshot) { g_take_screenshot = false; take_screenshot(g_hw_overlay); } } // end if sanity check passed // if sanity check failed else { static bool warned = false; // newbies might appreciate knowing why their video overlay isn't working, so // let's give them some instruction in the logfile. We only want to print this once // to avoid spamming, hence the 'warned' boolean if (!warned) { // if the game does not dynamically reallocate its overlay size (most of them don't) if (!g_game->is_overlay_size_dynamic()) { char s[81]; printline("WARNING : Your MPEG doesn't match your video overlay's resolution."); printline("Video overlay will not work!"); sprintf(s, "Your MPEG's size is %d x %d, and needs to be %d x %d", g_hw_overlay->w, g_hw_overlay->h, (gamevid->w << 1), (gamevid->h << 1)); printline(s); } // else, there is no problem at all, so don't alarm the user warned = true; } if (g_game->is_overlay_size_dynamic()) { // MPEG size has changed, so drop a hint to any game // that resizes dynamically. g_game->set_video_overlay_needs_update(true); } } // end sanity check result = VLDP_TRUE; // we were successful (we return successful even if overlay part failed because we want to render _something_) SDL_UnlockYUVOverlay(g_hw_overlay); } // end if locking the overlay was successful // else we are trying to feed info to the overlay too quickly, so we'll just have to wait return result; } // faster callback because it assumes we have no overlay // returns VLDP_TRUE on success, VLDP_FALSE on failure int prepare_frame_callback_without_overlay(struct yuv_buf *buf) { int result = VLDP_FALSE; // if locking the video overlay is successful if (SDL_LockYUVOverlay(g_hw_overlay) == 0) { buf2overlay_YUY2(g_hw_overlay, buf); // if we've been instructed to take a screenshot, do so now that the overlay is in place if (g_take_screenshot) { g_take_screenshot = false; take_screenshot(g_hw_overlay); } SDL_UnlockYUVOverlay(g_hw_overlay); result = VLDP_TRUE; } // else just ignore return result; } // displays the frame as fast as possible void display_frame_callback(struct yuv_buf *buf) { SDL_DisplayYUVOverlay(g_hw_overlay, g_screen_clip_rect); #if 0 { static unsigned int uOldTime = 0; static unsigned int uFrameCount = 0; unsigned int uTimer = refresh_ms_time(); unsigned int uDiff = uTimer - uOldTime; string strMsg = "[" + numstr::ToStr(g_vldp_info->current_frame) + "] Time since last frame change: " + numstr::ToStr(uDiff) + " ms"; FILE *F = fopen("vldp_log.txt", "ab"); if (F) { fprintf(F, "%s%c%c", strMsg.c_str(), 13, 10); fclose(F); } uOldTime = uTimer; } #endif } // This function converts the YV12-formatted 'src' to a YUY2-formatted overlay (which Xbox-Daphne may be using) // copies the contents of src into dst // assumes destination overlay is locked and *IMPORTANT* assumes src and dst are the same resolution void buf2overlay_YUY2(SDL_Overlay *dst, struct yuv_buf *src) { unsigned int channel0_pitch = dst->pitches[0]; Uint8 *dst_ptr = (Uint8 *) dst->pixels[0]; Uint8 *Y = (Uint8 *) src->Y; Uint8 *Y2 = ((Uint8 *) src->Y) + dst->w; Uint8 *U = (Uint8 *) src->U; Uint8 *V = (Uint8 *) src->V; int col, row; // do 2 rows at a time for (row = 0; row < (dst->h >> 1); row++) { // do 4 bytes at a time, but only iterate for w_half for (col = 0; col < (dst->w << 1); col += 4) { unsigned int Y_chunk = *((Uint16 *) Y); unsigned int Y2_chunk = *((Uint16 *) Y2); unsigned int V_chunk = *V; unsigned int U_chunk = *U; #if SDL_BYTEORDER == SDL_LIL_ENDIAN //Little-Endian (PC) *((Uint32 *) (g_line_buf + col)) = (Y_chunk & 0xFF) | (U_chunk << 8) | ((Y_chunk & 0xFF00) << 8) | (V_chunk << 24); *((Uint32 *) (g_line_buf2 + col)) = (Y2_chunk & 0xFF) | (U_chunk << 8) | ((Y2_chunk & 0xFF00) << 8) | (V_chunk << 24); #else //Big-Endian (Mac) *((Uint32 *) (g_line_buf + col)) = ((Y_chunk & 0xFF00) << 16) | ((U_chunk) << 16) | ((Y_chunk & 0xFF) << 8) | (V_chunk); *((Uint32 *) (g_line_buf2 + col)) = ((Y2_chunk & 0xFF00) << 16) | ((U_chunk) << 16) | ((Y2_chunk & 0xFF) << 8) | (V_chunk); #endif Y += 2; Y2 += 2; U++; V++; } // if we're not doing scanlines if (!(g_filter_type & FILTER_SCANLINES)) { // no filtering at all if (!(g_filter_type & FILTER_BLEND)) { memcpy(dst_ptr, g_line_buf, (dst->w << 1)); memcpy(dst_ptr + channel0_pitch, g_line_buf2, (dst->w << 1)); } else { g_blend_func(); // blend the two lines into g_line_buf3 // this won't affect video overlay because it is already doubled in size anyway memcpy(dst_ptr, g_line_buf3, (dst->w << 1)); memcpy(dst_ptr + channel0_pitch, g_line_buf3, (dst->w << 1)); } } // if we're doing scanlines else { // do a black YUY2 line first // (on nvidia it makes the top line too bright if the black line doesn't come first!) for (int i = 0; i < (dst->w << 1); i+=4) { *((Uint32 *) (dst_ptr + i)) = YUY2_BLACK; // this value is black in YUY2 mode } if (g_filter_type & FILTER_BLEND) { g_blend_func(); // blend the two lines into g_line_buf3 // this won't affect video overlay because it is already doubled in size anyway memcpy(dst_ptr + channel0_pitch, g_line_buf3, (dst->w << 1)); } else { memcpy(dst_ptr + channel0_pitch, g_line_buf, (dst->w << 1)); // this could also be g_line_buf2 } } dst_ptr += (channel0_pitch << 1); // we've done 2 rows, so skip a row Y += dst->w; // we've done 2 vertical Y pixels, so skip a row Y2 += dst->w; } } /////////////////// Uint32 g_parse_start_time = 0; // when mpeg parsing began approximately ... double g_parse_start_percentage = 0.0; // the first percentage report we received ... bool g_parsed = false; // whether we've received any data at all ... // this should be called from parent thread void update_parse_meter() { // if we have some data collected if (g_dPercentComplete01 >= 0) { double elapsed_s = 0; // how many seconds have elapsed since we began this ... double total_s = 0; // how many seconds the entire operation is likely to take double remaining_s = 0; // how many seconds are remaining double percent_complete = g_dPercentComplete01 * 100.0; // switch it from [0-1] to [0-100] elapsed_s = (elapsed_ms_time(g_parse_start_time)) * 0.001; // compute elapsed seconds double percentage_accomplished = percent_complete - g_parse_start_percentage; // how much 'percentage' points we've accomplished total_s = (elapsed_s * 100.0) / percentage_accomplished; // 100 signifies 100 percent (I got this equation by doing some algebra on paper) // as long as percent_complete is always 100 or lower, total_s will always be >= elapsed_s, so no checking necessary here remaining_s = total_s - elapsed_s; SDL_Surface *screen = get_screen_blitter(); // the main screen that we can draw on ... SDL_FillRect(screen, NULL, 0); // erase previous stuff on the screen blitter // if we have some progress to report ... if (remaining_s > 0) { char s[160]; int half_h = screen->h >> 1; // calculations to center message on screen ... int half_w = screen->w >> 1; sprintf(s, "Video parsing is %02.f percent complete, %02.f seconds remaining.\n", percent_complete, remaining_s); SDLDrawText(s, screen, FONT_SMALL, (half_w-((strlen(s)/2)*FONT_SMALL_W)), half_h-FONT_SMALL_H); // now draw a little graph thing ... SDL_Rect clip = screen->clip_rect; const int THICKNESS = 10; // how thick our little status bar will be clip.y = (clip.h - THICKNESS) / 2; // where to start our little status bar clip.h = THICKNESS; clip.y += FONT_SMALL_H + 5; // give us some padding SDL_FillRect(screen, &clip, SDL_MapRGB(screen->format, 255, 255, 255)); // draw a white bar across the screen ... clip.x++; // move left boundary in 1 pixel clip.y++; // move upper boundary down 1 pixel clip.w -= 2; // move right boundary in 1 pixel clip.h -= 2; // move lower boundary in 1 pixel SDL_FillRect(screen, &clip, SDL_MapRGB(screen->format, 0, 0, 0)); // fill inside with black clip.w = (Uint16) ((screen->w * g_dPercentComplete01) + 0.5)-1; // compute how wide our progress bar should be (-1 to take into account left pixel border) // go from full red (hardly complete) to full green (fully complete) SDL_FillRect(screen, &clip, SDL_MapRGB(screen->format, (Uint8) (255 * (1.0 - g_dPercentComplete01)), (Uint8) (255 * g_dPercentComplete01), 0)); } } } // percent_complete is between 0 and 1 // a negative value means that we are starting to parse a new file NOW void report_parse_progress_callback(double percent_complete_01) { g_dPercentComplete01 = percent_complete_01; g_bGotParseUpdate = true; g_parsed = true; // so we can know to re-create the overlay // if a new parse is starting if (percent_complete_01 < 0) { // NOTE : this would be a good place to automatically free the yuv overlay // BUT it was causing crashes... free_yuv_overlay here if you find any compatibility problems on other platforms g_parse_start_time = refresh_ms_time(); g_parse_start_percentage = 0; } } /////////////////// extern unsigned int g_draw_width, g_draw_height; // this always gets called before the draw_callback and always after report_parse_progress callback void report_mpeg_dimensions_callback(int width, int height) { unsigned int uTimer = refresh_ms_time(); // if we haven't blitted this information to the screen, then wait for other thread to do so before we continue ... while ((g_bGotParseUpdate) && (elapsed_ms_time(uTimer) < 3000)) { make_delay(1); } g_screen_clip_rect = &get_screen_blitter()->clip_rect; // used a lot, we only want to calculate it once // if draw width is less than the screen width if (g_draw_width < g_screen_clip_rect->w) { // center horizontally unsigned int uDiff = g_screen_clip_rect->w - g_draw_width; g_screen_clip_rect->x += (uDiff / 2); g_screen_clip_rect->w = g_draw_width; } // if draw height is less than the screen height if (g_draw_height < g_screen_clip_rect->h) { // center vertically unsigned int uDiff = g_screen_clip_rect->h - g_draw_height; g_screen_clip_rect->y += (uDiff / 2); g_screen_clip_rect->h = g_draw_height; } // if we drew some stuff on the screen, then we need to free the overlay and re-create it if (g_parsed) { free_yuv_overlay(); g_parsed = false; } // if an overlay exists, but its dimensions are wrong, we need to de-allocate it if (g_hw_overlay && ((g_hw_overlay->w != width) || (g_hw_overlay->h != height))) { free_yuv_overlay(); } // blitting is not allowed once we create the YUV overlay ... g_ldp->set_blitting_allowed(false); // if our overlay has been de-allocated, or if we never had one to begin with ... then allocate it now if (!g_hw_overlay) { // create overlay, taking into account any letterbox removal we're doing // (*4 because our pixels are *2 the height of the graphics, AND we're doing it at the top and bottom) g_hw_overlay = SDL_CreateYUVOverlay (width, height - (g_vertical_stretch * 4), SDL_YUY2_OVERLAY, get_screen()); // safety check if (!g_hw_overlay) { printline("ldp-vldp.cpp : YUV overlay creation failed!"); set_quitflag(); } // if overlay was successfully created, then indicate in the log whether it is HW accelerated or not else { string msg = "YUV overlay is done in software (ie unaccelerated)."; if (g_hw_overlay->hw_overlay) { msg = "YUV overlay is hardware accelerated."; } printline(msg.c_str()); // add it to the daphne_log.txt } // we don't need to check whether these buffers have been allocated or not because this is checked for earlier // when we check to see if g_hw_overlay has been allocated g_blank_yuv_buf.Y_size = width*height; g_blank_yuv_buf.Y = MPO_MALLOC(g_blank_yuv_buf.Y_size); memset(g_blank_yuv_buf.Y, 0, g_blank_yuv_buf.Y_size); // blank Y color g_blank_yuv_buf.UV_size = g_blank_yuv_buf.Y_size >> 2; g_blank_yuv_buf.U = MPO_MALLOC(g_blank_yuv_buf.UV_size); memset(g_blank_yuv_buf.U, 127, g_blank_yuv_buf.UV_size); // blank U color g_blank_yuv_buf.V = MPO_MALLOC(g_blank_yuv_buf.UV_size); memset(g_blank_yuv_buf.V, 127, g_blank_yuv_buf.UV_size); // blank V color // yuy2 needs twice as much space across for lines g_line_buf = MPO_MALLOC(width * 2); g_line_buf2 = MPO_MALLOC(width * 2); g_line_buf3 = MPO_MALLOC(width * 2); } // else g_hw_overlay exists, so we don't need to re-allocate it // This is some one-time setup stuff that will be used by the blending functions // It will be used by both the prepare w/ overlay and prepare w/o overlay functions if (g_filter_type & FILTER_BLEND) { g_blend_line1 = g_line_buf; g_blend_line2 = g_line_buf2; g_blend_dest = g_line_buf3; g_blend_iterations = g_hw_overlay->w << 1; #ifdef DEBUG assert(((g_blend_iterations % 8) == 0) && (g_blend_iterations >= 8)); // blend MMX does 8 bytes at a time #endif } } void free_yuv_overlay() { if (g_hw_overlay) { SDL_FreeYUVOverlay(g_hw_overlay); } g_hw_overlay = NULL; // free line bufs MPO_FREE(g_line_buf); MPO_FREE(g_line_buf2); MPO_FREE(g_line_buf3); // free blank buf ... MPO_FREE(g_blank_yuv_buf.Y); MPO_FREE(g_blank_yuv_buf.U); MPO_FREE(g_blank_yuv_buf.V); } // makes the laserdisc video black while drawing game's video overlay on top void blank_overlay() { // only do this if the HW overlay has already been allocated if (g_hw_overlay) { g_local_info.prepare_frame(&g_blank_yuv_buf); g_local_info.display_frame(&g_blank_yuv_buf); } }