singe/ldp-out/ldp-vldp.cpp
2019-11-11 14:53:02 -06:00

2288 lines
69 KiB
C++

/*
* 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 <assert.h>
#endif
//
#include <stdlib.h>
#include <time.h>
#include <set>
#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<string> 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<string, unsigned int>::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<string> &lstrPassed, list<string> &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<string> 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);
}
}