tge/engine/gui/game/guiAviBitmapCtrl.cc
2025-02-17 23:17:30 -06:00

1380 lines
36 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "gui/core/guiControl.h"
// This control currently disabled until streaming is
// added back into the audio library
#if defined(TORQUE_OS_LINUX) || defined(TORQUE_OS_OPENBSD)
#if DEDICATED
#define ENABLE_AVI_GUI 0
#define ENABLE_MPG_GUI 0
#else
#define ENABLE_AVI_GUI 0
#define ENABLE_MPG_GUI 0 // was 1
#endif
#else
/* Windows */
#define ENABLE_AVI_GUI 0
#define ENABLE_MPG_GUI 0
#endif
#if ENABLE_AVI_GUI || ENABLE_MPG_GUI
#include "audio/audio.h"
#if ENABLE_AVI_GUI
#include <windows.h>
#include <vfw.h>
#endif
#if ENABLE_MPG_GUI
#include "smpeg.h"
#endif
#endif /* Either AVI or MPG GUI */
#include "gui/game/guiAviBitmapCtrl.h"
#include "console/consoleTypes.h"
#include "dgl/dgl.h"
//----------------------------------------------------------------------------
#if !ENABLE_AVI_GUI || !ENABLE_MPG_GUI
// Version for Loki which will compile (and do nothing)-
IMPLEMENT_CONOBJECT(GuiAviBitmapCtrl);
GuiAviBitmapCtrl::GuiAviBitmapCtrl()
{
mDone = true;
}
GuiAviBitmapCtrl::~GuiAviBitmapCtrl()
{
}
void GuiAviBitmapCtrl::initPersistFields()
{
Parent::initPersistFields();
addGroup("Misc");
addField("done", TypeBool, Offset(mDone, GuiAviBitmapCtrl));
endGroup("Misc");
}
#else
// Code common to both AVI and MPG players
#define ALIGNULONG(bytes) ((((bytes) + 3) / 4) * 4)
// Error codes
#define MOVERR_OK 0
#define MOVERR_NOVIDEOSTREAM -1
#define MOVERR_PLAYING -4
//----------------------------------------------------------------------------
ConsoleMethod( GuiAviBitmapCtrl, setFilename, void, 3, 3, "(string filename)")
{
object->setFilename(argv[2]);
}
//----------------------------------------------------------------------------
ConsoleMethod( GuiAviBitmapCtrl, play, void, 2, 2, "Start playback.")
{
object->movieStart();
}
//----------------------------------------------------------------------------
ConsoleMethod( GuiAviBitmapCtrl, stop, void, 2, 2, "Stop playback.")
{
object->movieStop();
}
//////////////////////////////////////////////////////////////////////////////
// Audio Operations
bool GuiAviBitmapCtrl::sndOpen()
{
mAudioLatency = 0;
#if 0
// streaming disabled in this build
// Open up the audio. Taking the easy way and using two separate streams.
// Don't treat as error if it doesn't open-
dSprintf(fileBuffer, sizeof(fileBuffer), "%s", mWavFilename);
Audio::Description desc;
desc.mIs3D = false;
desc.mVolume = 1.0f;
desc.mIsLooping = false;
desc.mType = Audio::MusicAudioType;
mWavHandle = alxCreateSource(&desc, fileBuffer, 0);
#endif
mWavHandle = NULL_AUDIOHANDLE;
// alxGetContexti(ALC_BUFFER_LATENCY, &mAudioLatency);
return (mWavHandle != NULL_AUDIOHANDLE);
}
void GuiAviBitmapCtrl::sndStart()
{
if (mWavHandle != NULL_AUDIOHANDLE)
alxPlay(mWavHandle);
}
void GuiAviBitmapCtrl::sndStop()
{
if (mWavHandle != NULL_AUDIOHANDLE)
{
alxStop(mWavHandle);
mWavHandle = NULL_AUDIOHANDLE;
}
}
#if ENABLE_AVI_GUI
#define FOURCC_iv50 mmioFOURCC('i','v','5','0')
#define FOURCC_IV50 mmioFOURCC('I','V','5','0')
// Error codes
#define VIDSERR_OK 0
#define VIDSERR_NOVIDEO -20
#define VIDSERR_VCM -21
#define VIDSERR_NOTSTARTED -23
#define VIDSERR_READ -24
#define VCMERR_OK 0
#define VCMERR_INTERNAL -1
//----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT(GuiAviBitmapCtrl);
GuiAviBitmapCtrl::GuiAviBitmapCtrl()
{
mAviFilename = StringTable->insert("");
mTextureHandles = NULL;
mPFile = NULL;
mPAviVideo = NULL;
mBPlaying = false;
mDone = false;
mLetterBox = false;
mTimePlayStart = -1;
mPVBuf = NULL;
mPBuf = NULL;
mPBiSrc = mPBiDst = NULL;
mHic = NULL;
mFccHandler = 0;
mSwapRB = true;
mSpeed = 1.0;
mWavHandle = NULL_AUDIOHANDLE;
AVIFileInit();
}
//----------------------------------------------------------------------------
GuiAviBitmapCtrl::~GuiAviBitmapCtrl()
{
vcmClose();
AVIFileExit();
}
//////////////////////////////////////////////////////////////////////////////
// AVI File Operations
// Open a file
//
S32 GuiAviBitmapCtrl::fileOpen()
{
S32 rval;
if (!dStrcmp(mAviFilename,""))
return MOVERR_NOVIDEOSTREAM;
rval = AVIFileOpen(&mPFile, mAviFilename, OF_SHARE_DENY_WRITE, 0);
if (rval)
{
fileClose();
return rval;
}
return MOVERR_OK;
}
// Close a file
//
S32 GuiAviBitmapCtrl::fileClose ()
{
if (mPFile)
{
AVIFileRelease(mPFile);
mPFile = NULL;
}
return MOVERR_OK;
}
//////////////////////////////////////////////////////////////////////////////
// Movie Operations
// A movie must have a video stream
// Return codes should be properly done but for now, 0 is no error
// while !0 is error unless otherwise noted
// Open a movie (video stream) from an open file
//
S32 GuiAviBitmapCtrl::movieOpen()
{
S32 rval;
// Open first video stream found
// Note that we don't handle multiple video streams
rval = AVIFileGetStream(mPFile, &mPAviVideo, streamtypeVIDEO, 0);
if (rval == AVIERR_NODATA)
{
mPAviVideo = NULL;
return MOVERR_NOVIDEOSTREAM;
}
// Get video stream info
AVISTREAMINFO avis;
AVIStreamInfo(mPAviVideo, &avis, sizeof(avis));
mFrate = F32(avis.dwRate)/F32(avis.dwScale);
// Open vids
rval = vidsVideoOpen();
if (rval)
return rval;
return MOVERR_OK;
}
// Close movie stream
//
S32 GuiAviBitmapCtrl::movieClose()
{
// Make sure movie is stopped
movieStop();
// Release streams
if (mPAviVideo)
AVIStreamRelease(mPAviVideo);
mPAviVideo = 0;
return MOVERR_OK;
}
//////////////////////////////////////////////////////////////////////////////
// Video Operations
// Open and init VIDS
//
S32 GuiAviBitmapCtrl::vidsVideoOpen()
{
if (!mPAviVideo)
return VIDSERR_NOVIDEO;
mTimePlayStart = -1;
// Get stream info
AVISTREAMINFO avis;
AVIStreamInfo(mPAviVideo, &avis, sizeof(avis));
mVidsFirst = mVidsCurrent = avis.dwStart;
mVidsLast = avis.dwStart + avis.dwLength - 1;
mVidsPrevious = mVidsCurrent-1;
// This was not initialized and causing problems in RTEST-
mVidsPrevKey = -100000;
// Read first frame to get source info
S32 cb, rval;
BITMAPINFOHEADER biFormat;
AVIStreamFormatSize(mPAviVideo, 0, (long *) &cb);
if (cb != sizeof(BITMAPINFOHEADER))
return -1;
rval = AVIStreamReadFormat(mPAviVideo, 0, &biFormat, (long *) &cb);
rval = rval;
// Open VCM for this instance
if (vcmOpen(avis.fccHandler, &biFormat))
{
vidsVideoClose();
return VIDSERR_VCM;
}
// Create temporary buffer
mCBVBuf = biFormat.biHeight * ALIGNULONG(biFormat.biWidth)
* biFormat.biBitCount/8;
mPVBuf = (unsigned char *) dMalloc(mCBVBuf);
AssertFatal(mPVBuf, "Out of memory");
dMemset(mPVBuf, 0, mCBVBuf);
// Reset internal skip count
mPlayFSkipped = 0;
return VIDSERR_OK;
}
// Close VIDS
//
S32 GuiAviBitmapCtrl::vidsVideoClose()
{
if (mPVBuf)
{
dFree(mPVBuf);
mPVBuf = NULL;
}
vcmClose();
return VIDSERR_OK;
}
// Start video, note that all decode/draw is done in vidsDraw()
//
// If fStart < 0, then start on current frame
//
S32 GuiAviBitmapCtrl::vidsVideoStart()
{
// Begin VCM
if (vcmBegin())
{
vidsVideoStop();
return VIDSERR_VCM;
}
// Pass seperate start message to VCM only when playing
if (mBPlaying)
vcmDrawStart();
// Get start time
mTimePlayStart = getMilliseconds();
mTimePlayStartPos = AVIStreamSampleToTime(mPAviVideo, mVidsCurrent);
// Init play stats
mPlayFPrev = mVidsCurrent;
return VIDSERR_OK;
}
// Stop video
//
S32 GuiAviBitmapCtrl::vidsVideoStop()
{
vcmDrawStop();
vcmEnd();
mTimePlayStart = -1;
return VIDSERR_OK;
}
// Draw current frame of video
//
S32 GuiAviBitmapCtrl::vidsVideoDraw()
{
// Check if started
if (mTimePlayStart < 0)
return VIDSERR_NOTSTARTED;
if (mBPlaying)
{
S32 lTime = mTimePlayStartPos + (getMilliseconds() - mTimePlayStart);
mVidsCurrent = vidsTimeToSample(lTime);
if (mVidsCurrent > mVidsLast)
mVidsCurrent = mVidsLast;
if (mVidsCurrent == mVidsPrevious)
// Going too fast! Should actually return a ms
// count so calling app can Sleep() if desired.
return VIDSERR_OK;
}
else
{
if (mVidsCurrent > mVidsLast)
mVidsCurrent = mVidsLast;
if (mVidsCurrent == mVidsPrevious)
vidsResetDraw();
}
if (!vidsSync())
return VIDSERR_OK; // don't draw this frame
S32 rval = AVIStreamRead(mPAviVideo,
mVidsCurrent,
1,
mPVBuf,
mCBVBuf,
NULL,
NULL);
if (rval)
return VIDSERR_READ;
if (vcmDraw())
return VIDSERR_VCM;
mVidsPrevious = mVidsCurrent;
if (mBPlaying)
{
mPlayFSkipped += mVidsCurrent-mPlayFPrev-1;
mPlayFPrev = mVidsCurrent;
if (mVidsCurrent == mVidsLast)
{
mVidsCurrent = -1;
return movieStop();
}
}
return VIDSERR_OK;
}
// Convert ms to sample (frame)
//
S32 GuiAviBitmapCtrl::vidsTimeToSample(S32 lTime)
{
S32 lSamp = AVIStreamTimeToSample(mPAviVideo, lTime);
return lSamp;
}
// TRUE if frame is KEY, if frame < 0 then check current frame
//
bool GuiAviBitmapCtrl::vidsIsKey(S32 frame /* = -1 */)
{
if (!mPAviVideo)
return false;
if (frame < 0)
frame = mVidsCurrent;
return AVIStreamIsKeyFrame(mPAviVideo, frame);
}
//////////////////////////////////////////////////////////////////////////////
// Internal video routines
// Synchronization and Keyframe Management:
// pretty simple plan, don't do anything too fancy.
//
bool GuiAviBitmapCtrl::vidsSync()
{
#define dist(x,y) ((x)-(y))
#define ABS_(x) (x<0 ? (-(x)) : (x))
if (mVidsCurrent < mVidsPrevious) // seeked back - reset draw
mVidsPrevious = -1;
if (dist(mVidsCurrent, mVidsPrevious) == 1)
{
// normal situation
// fall thru and draw
}
else
{
// SKIPPED
if (AVIStreamIsKeyFrame(mPAviVideo, mVidsCurrent))
{
// we are on KF boundry just reset and start here
mVidsPrevKey = mVidsCurrent;
mVidsNextKey = AVIStreamNextKeyFrame(mPAviVideo, mVidsCurrent);
// fall thru and draw
}
else
{
if (dist(mVidsCurrent, mVidsPrevious) == 2)
{
// one frame off - just draw
vidsCatchup();
// fall thru and draw
}
else
{
// We are greater than one frame off:
// if we went past a K frame, update K frame info then:
// if we are closer to previous frame than catchup and draw
// if we are closer to next KEY frame than don't draw
if ((mVidsNextKey < mVidsCurrent) || (mVidsPrevKey > mVidsCurrent)) // seeked past previous key frame
{
// went past a K frame
mVidsPrevKey = AVIStreamPrevKeyFrame (mPAviVideo, mVidsCurrent);
mVidsNextKey = AVIStreamNextKeyFrame (mPAviVideo, mVidsCurrent);
}
if (ABS_(dist(mVidsCurrent, mVidsPrevKey)) <= ABS_(dist(mVidsCurrent, mVidsNextKey)))
vidsCatchup();
// fall thru and draw
else
if (mBPlaying)
return false; // m_vidsPrev NOT updated
else
vidsCatchup(); // if not playing than we want to
// draw the current frame
}
}
}
return true;
}
// Readies to draw (but doesn't draw) m_vidsCurrent.
// We just ICDECOMPRESS_HURRYUP frames from m_vidsPrevious or
// m_vidsPrevKey, whichever is closer.
// Updates m_vidsPrevious.
//
void GuiAviBitmapCtrl::vidsCatchup()
{
if (mVidsPrevious < mVidsPrevKey)
mVidsPrevious = mVidsPrevKey-1;
S32 catchup = mVidsPrevious+1;
while (catchup < mVidsCurrent)
{
S32 rval = AVIStreamRead(mPAviVideo,
catchup,
1,
mPVBuf,
mCBVBuf,
NULL,
NULL);
if (rval)
break;
if (!mBPlaying )
vcmDrawIn();
else
vcmDrawIn(ICDECOMPRESS_HURRYUP);
mVidsPrevious = catchup++;
}
}
// Note that between vcmOpen() and vcmClose(), the source information does not
// change. If it does, open a new one.
//
S32 GuiAviBitmapCtrl::vcmOpen (FOURCC fccHandler, BITMAPINFOHEADER * pbiSrc)
{
if (fccHandler == 0)
mFccHandler = pbiSrc->biCompression;
else
mFccHandler = fccHandler;
if (mFccHandler == FOURCC_IV50)
mFccHandler = FOURCC_iv50;
// Open codec
mHic = ICLocate(ICTYPE_VIDEO, fccHandler, pbiSrc, 0, ICMODE_DECOMPRESS);
if (!mHic) return AVIERR_NOCOMPRESSOR;
delete [] mPBiSrc;
mPBiSrc = (BITMAPINFOHEADER *) new char [sizeof (BITMAPINFOHEADER) +
256 * sizeof (RGBQUAD)];
delete [] mPBiDst;
mPBiDst = (BITMAPINFOHEADER *) new char [sizeof (BITMAPINFOHEADER) +
256 * sizeof (RGBQUAD)];
AssertFatal(mPBiSrc && mPBiDst, "Out of memory");
dMemcpy(mPBiSrc, pbiSrc, sizeof(BITMAPINFOHEADER));
// Initialize destination bitmap header
dMemcpy (mPBiDst, mPBiSrc, sizeof(BITMAPINFOHEADER));
// Default destination bitmap header
mPBiDst->biBitCount = 24;
mPBiDst->biCompression = BI_RGB;
mPBiDst->biSizeImage = mPBiDst->biHeight * ALIGNULONG(mPBiDst->biWidth)*mPBiDst->biBitCount/8;
// Create temporary buffer
mCBuf = mPBiDst->biSizeImage;
mPBuf = (U8 *) dMalloc(mCBuf);
AssertFatal(mPBuf, "Out of memory");
dMemset(mPBuf, 0, mCBuf);
return VCMERR_OK;
}
S32 GuiAviBitmapCtrl::vcmClose()
{
if (mPBiSrc) delete [] mPBiSrc;
if (mPBiDst) delete [] mPBiDst;
mPBiSrc = mPBiDst = NULL;
if (mPBuf)
{
dFree(mPBuf);
mPBuf = NULL;
}
if (mHic)
{
ICClose(mHic);
mHic = NULL;
}
mFccHandler = 0;
return VCMERR_OK;
}
// vcmBegin() and vcmEnd() (de)initializes a series of vcmDraw()'s
// The user must end and restart a sequence when the destination
// parameters change.
// Note that if the source information changes vcmOpen()/vcmClose()
// must be used (since fcc might be different).
// fInit initializes the sequence (do the first time and when resetting
// parameters
//
S32 GuiAviBitmapCtrl::vcmBegin()
{
S32 rval;
if (!mHic)
return VCMERR_INTERNAL;
rval = ICDecompressExQuery(mHic, 0,
mPBiSrc, NULL, 0, 0, mBitmapWidth, mBitmapHeight,
mPBiDst, NULL, 0, 0, mBitmapWidth, mBitmapHeight);
if (rval) return rval;
rval = ICDecompressExBegin(mHic, 0,
mPBiSrc, NULL, 0, 0, mBitmapWidth, mBitmapHeight,
mPBiDst, NULL, 0, 0, mBitmapWidth, mBitmapHeight);
if (rval) return rval;
return AVIERR_OK;
}
S32 GuiAviBitmapCtrl::vcmEnd()
{
return VCMERR_OK;
}
// vcmDrawStart/vcmDrawStop are not absolutely necessary but some codecs
// use them to do timing (to tell when playing real time)
//
S32 GuiAviBitmapCtrl::vcmDrawStart()
{
// Send ICM_DRAW_BEGIN.
// this is only for telling the codec what our frame rate is - zero out all other members.
ICDrawBegin(mHic,
0, 0, 0, 0,
0, 0, 0, 0,
NULL,
0, 0, 0, 0,
(DWORD) (1.0/mFrate * 1000.0 * 1000.0), // dwRate
(DWORD) (1000*1000)); // dwScale
// Send ICM_DRAW_START.
ICDrawStart(mHic);
return VCMERR_OK;
}
S32 GuiAviBitmapCtrl::vcmDrawStop()
{
// Send ICM_DRAW_STOP
ICDrawStop(mHic);
// Send ICM_DRAW_END
ICDrawEnd(mHic);
return VCMERR_OK;
}
S32 GuiAviBitmapCtrl::vcmDraw(U64 dwICflags)
{
S32 rval;
rval = ICDecompressEx(mHic, dwICflags,
mPBiSrc, mPVBuf, 0, 0, mBitmapWidth, mBitmapHeight,
mPBiDst, mPBuf, 0, 0, mBitmapWidth, mBitmapHeight);
if (rval)
// Normal in case of ICM_HURRYUP flag (rval = 1)
return rval;
for (U32 j = 0; j < mHeightCount; j++)
{
U32 y = j * 256;
U32 height = getMin(mBitmapHeight - y, U32(256));
for (U32 i = 0; i < mWidthCount; i++)
{
U32 index = j * mWidthCount + i;
U32 x = i * 256;
U32 width = getMin(mBitmapWidth - x, U32(256));
GBitmap *bmp = mTextureHandles[index].getBitmap();
for (U32 lp = 0; lp < height; lp++)
{
const U8 *src = &mPBuf[3*((mBitmapHeight-(y+lp+1))*mBitmapAlignedWidth + x)];
U8 *dest = bmp->getAddress(0, lp);
// counting on the artist to switch the R & B channels so we don't have to in runtime
if (!mSwapRB)
dMemcpy(dest, src, width*3);
else
for (U32 k = 0; k < width; ++k)
{
*dest++ = src[2];
*dest++ = src[1];
*dest++ = src[0];
src += 3;
}
}
mTextureHandles[index].refresh();
}
}
return VCMERR_OK;
}
S32 GuiAviBitmapCtrl::vcmDrawIn(U64 dwICflags)
{
// If we are not displaying frames, IVI still writes to the buffer
S32 rval = ICDecompressEx(mHic, dwICflags,
mPBiSrc, mPVBuf, 0, 0, mBitmapWidth, mBitmapHeight,
mPBiDst, mPBuf, 0, 0, mBitmapWidth, mBitmapHeight);
if (rval)
// Normal in case of ICM_HURRYUP flag (rval = 1)
return rval;
return VCMERR_OK;
}
void GuiAviBitmapCtrl::initPersistFields()
{
Parent::initPersistFields();
addGroup("Media");
addField("aviFilename", TypeFilename, Offset(mAviFilename, GuiAviBitmapCtrl));
addField("wavFilename", TypeFilename, Offset(mWavFilename, GuiAviBitmapCtrl));
endGroup("Media");
addGroup("Misc");
addField("swapRB", TypeBool, Offset(mSwapRB, GuiAviBitmapCtrl));
addField("done", TypeBool, Offset(mDone, GuiAviBitmapCtrl));
addField("letterBox", TypeBool, Offset(mLetterBox, GuiAviBitmapCtrl));
addField("speed", TypeF32, Offset(mSpeed, GuiAviBitmapCtrl));
endGroup("Misc");
}
//----------------------------------------------------------------------------
void GuiAviBitmapCtrl::setFilename(const char *filename)
{
bool awake = mAwake;
if (awake)
onSleep();
mAviFilename = StringTable->insert(filename);
if (awake)
onWake();
}
// Start a movie, i.e. begin play from stopped state
// or restart from paused state
//
S32 GuiAviBitmapCtrl::movieStart()
{
if (!mPAviVideo)
return MOVERR_NOVIDEOSTREAM;
// Check if starting without stopping
if (mBPlaying)
return MOVERR_PLAYING;
mBPlaying = true;
sndStart();
// Start video, note only one state var, play or stop
vidsVideoStart();
setUpdate();
return MOVERR_OK;
}
// Stop playing a movie
//
S32 GuiAviBitmapCtrl::movieStop()
{
mBPlaying = false;
vidsVideoStop();
sndStop();
// notify the script
// Con::executef(this,1,"movieStopped");
mDone = true;
return MOVERR_OK;
}
//----------------------------------------------------------------------------
bool GuiAviBitmapCtrl::onWake()
{
if (!Parent::onWake()) return false;
if (fileOpen() || movieOpen())
{
mDone = true;
// we return TRUE here, or else the damn thing gets deleted and that's
// just plain bad.
return true;
}
sndOpen();
mBitmapWidth = mPBiSrc->biWidth;
mBitmapAlignedWidth = ALIGNULONG(mBitmapWidth);
mBitmapHeight = mPBiSrc->biHeight;
mWidthCount = mBitmapWidth / 256;
mHeightCount = mBitmapHeight / 256;
if (mBitmapWidth % 256)
mWidthCount++;
if (mBitmapHeight % 256)
mHeightCount++;
mNumTextures = mWidthCount * mHeightCount;
mTextureHandles = new TextureHandle[mNumTextures];
for (U32 j = 0; j < mHeightCount; j++)
{
U32 y = j * 256;
U32 height = getMin(mBitmapWidth - y, U32(256));
for (U32 i = 0; i < mWidthCount; i++)
{
char nameBuffer[64];
U32 index = j * mWidthCount + i;
dSprintf(nameBuffer, sizeof(nameBuffer), "%s_#%d_#%d", mAviFilename, i, j);
mTextureHandles[index] = TextureHandle(nameBuffer, RegisteredTexture, true);
if (!bool(mTextureHandles[index]))
{
U32 x = i * 256;
U32 width = getMin(mBitmapWidth - x, U32(256));
const GBitmap *bmp = new GBitmap(width, height, false, GBitmap::RGB);
mTextureHandles[index] = TextureHandle(nameBuffer, bmp, true);
}
}
}
return true;
}
//----------------------------------------------------------------------------
void GuiAviBitmapCtrl::onSleep()
{
movieClose();
fileClose();
if (mTextureHandles)
{
delete [] mTextureHandles;
mTextureHandles = NULL;
}
Parent::onSleep();
}
//----------------------------------------------------------------------------
void GuiAviBitmapCtrl::onMouseDown(const GuiEvent&)
{
// end the movie NOW!
movieStop();
}
//----------------------------------------------------------------------------
bool GuiAviBitmapCtrl::onKeyDown(const GuiEvent&)
{
// end the movie NOW!
movieStop();
return true;
}
//----------------------------------------------------------------------------
// Playing with speed for debugging (glitch shows up when skipping occurs).
S32 GuiAviBitmapCtrl::getMilliseconds()
{
F32 ms;
if (mSpeed > 0)
{
ms = Platform::getRealMilliseconds();
ms *= mSpeed;
}
else
{
// Try to force the glitch (negative speed)-
static F32 deterministicClock = 0.0f;
ms = deterministicClock;
deterministicClock -= mSpeed;
}
return S32(ms);
}
//----------------------------------------------------------------------------
void GuiAviBitmapCtrl::onRender(Point2I offset, const RectI &updateRect)
{
vidsVideoDraw();
if (mTextureHandles)
{
RectI displayRect(mBounds);
S32 verticalDisplace = 0;
if (mLetterBox)
{
// Our supplied picture is 3/4 height 640x360 for 640x480 res. But let's allow
// for a regular full-window version since it might come in handy elsewhere (so
// letterBox is public flag on the GUI).
verticalDisplace = (mBounds.extent.y >> 3);
displayRect.extent.y = (displayRect.extent.y * 3 >> 2);
displayRect.point.y += verticalDisplace;
RectI upperRect(offset.x, offset.y, displayRect.extent.x, verticalDisplace + 1);
dglDrawRectFill(upperRect, mProfile->mFillColorHL);
}
// Scale into the letterbox-
F32 widthScale = F32(displayRect.extent.x) / F32(mBitmapWidth);
F32 heightScale = F32(displayRect.extent.y) / F32(mBitmapHeight);
offset.y += verticalDisplace;
dglSetBitmapModulation(ColorF(1,1,1));
for (U32 i = 0; i < mWidthCount; i++)
{
for (U32 j = 0; j < mHeightCount; j++)
{
TextureHandle t = mTextureHandles[j * mWidthCount + i];
RectI stretchRegion;
stretchRegion.point.x = i * 256 * widthScale + offset.x;
stretchRegion.point.y = j * 256 * heightScale + offset.y;
stretchRegion.extent.x = (i * 256 + t.getWidth()) * widthScale + offset.x - stretchRegion.point.x;
stretchRegion.extent.y = (j * 256 + t.getHeight()) * heightScale + offset.y - stretchRegion.point.y;
dglDrawBitmapStretch(t, stretchRegion);
}
}
if (mLetterBox)
{
// For some reason the above loop draws white below, and this rect fill has to
// come after - got to look at that math... Also don't know why we need the
// extra width & height here...
RectI lowerRect(offset.x, mBounds.extent.y - verticalDisplace - 1,
mBounds.extent.x + 2, verticalDisplace + 2);
dglDrawRectFill(lowerRect, mProfile->mFillColorHL);
}
renderChildControls(offset, updateRect);
if (mBPlaying)
setUpdate();
}
else
Parent::onRender(offset, updateRect);
}
#endif /* ENABLE_AVI_GUI */
#if ENABLE_MPG_GUI
//----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT(GuiAviBitmapCtrl);
GuiAviBitmapCtrl::GuiAviBitmapCtrl()
{
mAviFilename = StringTable->insert("");
mTextureHandles = NULL;
mMPEG = NULL;
mBPlaying = false;
mDone = false;
mLetterBox = false;
mSurface = NULL;
mPBuf = NULL;
mDecodeLock = NULL;
mWavHandle = NULL_AUDIOHANDLE;
}
//----------------------------------------------------------------------------
GuiAviBitmapCtrl::~GuiAviBitmapCtrl()
{
}
//////////////////////////////////////////////////////////////////////////////
// MPEG File Operations
// Open a file
//
S32 GuiAviBitmapCtrl::fileOpen()
{
char fileBuffer[256];
if (!dStrcmp(mAviFilename,""))
return MOVERR_NOVIDEOSTREAM;
// FixMe: Should convert this to have a dir attribute...
dSprintf(fileBuffer, sizeof(fileBuffer), "FixMe/textures/%s", mAviFilename);
// Convert filename from .avi to .mpg
char *ext;
ext = dStrstr(static_cast<const char *>(fileBuffer), ".avi");
if (!ext)
ext = dStrstr(static_cast<const char *>(fileBuffer), ".AVI");
if (ext)
dStrcpy(ext, ".mpg");
mMPEG = SMPEG_new(fileBuffer, &mInfo, 0);
if (!mMPEG || (SMPEG_status(mMPEG) == SMPEG_ERROR))
{
fileClose();
return MOVERR_NOVIDEOSTREAM;
}
return MOVERR_OK;
}
// Close a file
//
S32 GuiAviBitmapCtrl::fileClose ()
{
if (mMPEG)
{
SMPEG_delete(mMPEG);
mMPEG = NULL;
}
return MOVERR_OK;
}
//////////////////////////////////////////////////////////////////////////////
// Movie Operations
// A movie must have a video stream
// Return codes should be properly done but for now, 0 is no error
// while !0 is error unless otherwise noted
// Open a movie (video stream) from an open file
//
S32 GuiAviBitmapCtrl::movieOpen()
{
// If the file was opened successfully, it's an MPEG video
return MOVERR_OK;
}
// Close movie stream
//
S32 GuiAviBitmapCtrl::movieClose()
{
// Make sure movie is stopped
movieStop();
return MOVERR_OK;
}
void GuiAviBitmapCtrl::initPersistFields()
{
Parent::initPersistFields();
addGroup("Media");
addField("aviFilename", TypeFilename, Offset(mAviFilename, GuiAviBitmapCtrl));
addField("wavFilename", TypeFilename, Offset(mWavFilename, GuiAviBitmapCtrl));
endGroup("Media");
addGroup("Misc");
addField("done", TypeBool, Offset(mDone, GuiAviBitmapCtrl));
addField("letterBox", TypeBool, Offset(mLetterBox, GuiAviBitmapCtrl));
endGroup("Misc");
}
//----------------------------------------------------------------------------
void GuiAviBitmapCtrl::setFilename(const char *filename)
{
bool awake = mAwake;
if (awake)
onSleep();
mAviFilename = StringTable->insert(filename);
if (awake)
onWake();
}
// Start a movie, i.e. begin play from stopped state
// or restart from paused state
//
S32 GuiAviBitmapCtrl::movieStart()
{
if (!mMPEG)
return MOVERR_NOVIDEOSTREAM;
// Check if starting without stopping
if (mBPlaying)
return MOVERR_PLAYING;
mBPlaying = true;
sndStart();
// Start video, note only one state var, play or stop
SMPEG_play(mMPEG);
return MOVERR_OK;
}
// Stop playing a movie
//
S32 GuiAviBitmapCtrl::movieStop()
{
mBPlaying = false;
if (mMPEG)
{
SMPEG_stop(mMPEG);
}
sndStop();
// notify the script
// Con::executef(this,1,"movieStopped");
mDone = true;
return MOVERR_OK;
}
//----------------------------------------------------------------------------
bool GuiAviBitmapCtrl::onWake()
{
if (!Parent::onWake()) return false;
if (fileOpen() || movieOpen())
{
mDone = true;
// Never return false from onWake, the object will be freed, but
// not removed from the gui framework, so the game crashes later.
return true;
}
sndOpen();
mBitmapWidth = mInfo.width;
mBitmapAlignedWidth = ALIGNULONG(mBitmapWidth);
AssertFatal(mBitmapAlignedWidth == mBitmapWidth, "Unaligned MPEG data");
mBitmapHeight = mInfo.height;
mWidthCount = mBitmapWidth / 256;
mHeightCount = mBitmapHeight / 256;
if (mBitmapWidth % 256)
mWidthCount++;
if (mBitmapHeight % 256)
mHeightCount++;
mNumTextures = mWidthCount * mHeightCount;
mTextureHandles = new TextureHandle[mNumTextures];
for (U32 j = 0; j < mHeightCount; j++)
{
U32 y = j * 256;
U32 height = getMin(mBitmapWidth - y, U32(256));
for (U32 i = 0; i < mWidthCount; i++)
{
char nameBuffer[64];
U32 index = j * mWidthCount + i;
dSprintf(nameBuffer, sizeof(nameBuffer), "%s_#%d_#%d", mAviFilename, i, j);
mTextureHandles[index] = TextureHandle(nameBuffer, RegisteredTexture, true);
if (!bool(mTextureHandles[index]))
{
U32 x = i * 256;
U32 width = getMin(mBitmapWidth - x, U32(256));
const GBitmap *bmp = new GBitmap(width, height, false, GBitmap::RGB);
mTextureHandles[index] = TextureHandle(nameBuffer, bmp, true);
}
}
}
// Allocate the SDL surface for the YUV decoding
// It shore wood be nice if SDL could decode YUV to GL textures...
mPBuf = (U8 *) dMalloc(mBitmapWidth*3*mBitmapHeight);
AssertFatal(mPBuf, "Out of memory for buffer.");
mSurface = SDL_CreateRGBSurfaceFrom(mPBuf,
mBitmapWidth, mBitmapHeight, 24,
mBitmapWidth*3,
0x000000FF, 0x0000FF00, 0x00FF0000, 0);
AssertFatal(mSurface, "Out of memory for a surface!");
// Target the decoding to our surface
mDecodeLock = SDL_CreateMutex();
AssertFatal(mDecodeLock, "Out of memory for a decode lock!");
SMPEG_setdisplay(mMPEG, mSurface, mDecodeLock, NULL);
return true;
}
//----------------------------------------------------------------------------
void GuiAviBitmapCtrl::onSleep()
{
movieClose();
fileClose();
if (mSurface)
{
SDL_FreeSurface(mSurface);
mSurface = NULL;
}
if (mPBuf)
{
dFree(mPBuf);
mPBuf = NULL;
}
if (mTextureHandles)
{
delete [] mTextureHandles;
mTextureHandles = NULL;
}
Parent::onSleep();
}
//----------------------------------------------------------------------------
void GuiAviBitmapCtrl::onMouseDown(const GuiEvent&)
{
// end the movie NOW!
movieStop();
}
//----------------------------------------------------------------------------
bool GuiAviBitmapCtrl::onKeyDown(const GuiEvent&)
{
// end the movie NOW!
movieStop();
return true;
}
//----------------------------------------------------------------------------
void GuiAviBitmapCtrl::onRender(Point2I offset, const RectI &updateRect)
{
if (mTextureHandles)
{
RectI displayRect(mBounds);
S32 verticalDisplace = 0;
// Get the converted RGB data from SMPEG
SDL_LockMutex(mDecodeLock);
for (U32 j = 0; j < mHeightCount; j++)
{
U32 y = j * 256;
U32 height = getMin(mBitmapHeight - y, U32(256));
for (U32 i = 0; i < mWidthCount; i++)
{
U32 index = j * mWidthCount + i;
U32 x = i * 256;
U32 width = getMin(mBitmapWidth - x, U32(256));
GBitmap *bmp = mTextureHandles[index].getBitmap();
for (U32 lp = 0; lp < height; lp++)
{
const U8 *src = &mPBuf[3*((y+lp)*mBitmapAlignedWidth + x)];
U8 *dest = bmp->getAddress(0, lp);
dMemcpy(dest, src, width*3);
}
mTextureHandles[index].refresh();
}
}
SDL_UnlockMutex(mDecodeLock);
if (mLetterBox)
{
// Our supplied picture is 3/4 height 640x360 for 640x480 res. But let's allow
// for a regular full-window version since it might come in handy elsewhere (so
// letterBox is public flag on the GUI).
verticalDisplace = (mBounds.extent.y >> 3);
displayRect.extent.y = (displayRect.extent.y * 3 >> 2);
displayRect.point.y += verticalDisplace;
RectI upperRect(offset.x, offset.y, displayRect.extent.x, verticalDisplace + 1);
dglDrawRectFill(upperRect, mProfile->mFillColorHL);
}
// Scale into the letterbox-
F32 widthScale = F32(displayRect.extent.x) / F32(mBitmapWidth);
F32 heightScale = F32(displayRect.extent.y) / F32(mBitmapHeight);
offset.y += verticalDisplace;
dglSetBitmapModulation(ColorF(1,1,1));
for (U32 i = 0; i < mWidthCount; i++)
{
for (U32 j = 0; j < mHeightCount; j++)
{
TextureHandle t = mTextureHandles[j * mWidthCount + i];
RectI stretchRegion;
stretchRegion.point.x = i * 256 * widthScale + offset.x;
stretchRegion.point.y = j * 256 * heightScale + offset.y;
stretchRegion.extent.x = (i * 256 + t.getWidth()) * widthScale + offset.x - stretchRegion.point.x;
stretchRegion.extent.y = (j * 256 + t.getHeight()) * heightScale + offset.y - stretchRegion.point.y;
dglDrawBitmapStretch(t, stretchRegion);
}
}
if (mLetterBox)
{
// For some reason the above loop draws white below, and this rect fill has to
// come after - got to look at that math... Also don't know why we need the
// extra width & height here...
RectI lowerRect(offset.x, mBounds.extent.y - verticalDisplace - 1,
mBounds.extent.x + 2, verticalDisplace + 2);
dglDrawRectFill(lowerRect, mProfile->mFillColorHL);
}
renderChildControls(offset, updateRect);
mBPlaying = (SMPEG_status(mMPEG) == SMPEG_PLAYING);
if (mBPlaying)
setUpdate();
else
movieStop();
}
else
Parent::onRender(offset, updateRect);
}
#endif /* ENABLE_MPG_GUI */
#endif /* Enabled Movie GUI */