//----------------------------------------------------------------------------- // 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 #include #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(fileBuffer), ".avi"); if (!ext) ext = dStrstr(static_cast(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 */