//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "gfx/video/videoCapture.h" #include "console/console.h" #include "core/strings/stringFunctions.h" #include "core/util/journal/journal.h" #include "core/module.h" #include "gui/core/guiCanvas.h" #include "gfx/gfxTextureManager.h" #include "console/engineAPI.h" Vector VideoCapture::mEncoderFactoryFnList; MODULE_BEGIN( VideoCapture ) MODULE_INIT_BEFORE( GFX ) MODULE_SHUTDOWN_BEFORE( GFX ) MODULE_INIT { ManagedSingleton< VideoCapture >::createSingleton(); } MODULE_SHUTDOWN { VIDCAP->end(); ManagedSingleton< VideoCapture >::deleteSingleton(); } MODULE_END; VideoCapture::VideoCapture() : mCapturedFramePos(-1.0f), mEncoder(NULL), mFrameGrabber(NULL), mCanvas(NULL), mIsRecording(false), mVideoCaptureStartTime(0), mNextFramePosition(0.0f), mFrameRate(30.0f), mMsPerFrame(1000.0f / mFrameRate), mResolution(0,0), mWaitingForCanvas(false), mEncoderName("THEORA"), mMsPerFrameError(0), mFileName("") { } S32 VideoCapture::getMsPerFrame() { //Add accumulated error to ms per frame before rounding F32 roundTime = mFloor(mMsPerFrame + mMsPerFrameError + 0.5f); //Accumulate the rounding errors mMsPerFrameError += mMsPerFrame - roundTime; return (S32)roundTime; } void VideoCapture::begin( GuiCanvas* canvas ) { // No longer waiting for a canvas mWaitingForCanvas = false; // No specified file if (mFileName.isEmpty()) { Con::errorf("VideoCapture: no file specified!"); return; } // No framegrabber, cannot capture if (mFrameGrabber == NULL) { Con::errorf("VideoCapture: cannot capture without a VideoFrameGrabber! One should be created in the GFXDevice initialization!"); return; } // Set the active encoder if (!initEncoder(mEncoderName)) return; // Store the canvas, so we know which one to capture from mCanvas = canvas; // If the resolution is zero, get the current video mode if (mResolution.isZero()) mResolution = mCanvas->getPlatformWindow()->getVideoMode().resolution; // Set the encoder file, framerate and resolution mEncoder->setFile(mFileName); mEncoder->setFramerate( &mFrameRate ); mEncoder->setResolution( &mResolution ); // The frame grabber must know about the resolution as well, since it'll do the resizing for us mFrameGrabber->setOutResolution( mResolution ); // Calculate the ms per frame mMsPerFrame = 1000.0f / mFrameRate; // Start the encoder if (!mEncoder->begin()) return; // We're now recording mIsRecording = true; mNextFramePosition = 0.0f; } void VideoCapture::end() { if (!mIsRecording) return; if (mEncoder && !mEncoder->end()) Con::errorf("VideoCapture: an error has ocurred while closing the video stream"); // Garbage collect the processed bitmaps deleteProcessedBitmaps(); delete mEncoder; mEncoder = NULL; mIsRecording = false; } void VideoCapture::capture() { // If this is the first frame, capture and encode it right away if (mNextFramePosition == 0.0f) { mVideoCaptureStartTime = Platform::getVirtualMilliseconds(); mCapturedFramePos = -1.0f; } // Calculate the frame position for this captured frame U32 frameTimeMs = Platform::getVirtualMilliseconds() - mVideoCaptureStartTime; F32 framePosition = (F32)frameTimeMs / mMsPerFrame; // Repeat until the current frame is captured while (framePosition > mCapturedFramePos) { // If the frame position is closer to the next frame position // than the previous one capture it if ( mFabs(framePosition - mNextFramePosition) < mFabs(mCapturedFramePos - mNextFramePosition) ) { mFrameGrabber->captureBackBuffer(); mCapturedFramePos = framePosition; } // If the new frame position is greater or equal than the next frame time // tell the framegrabber to make bitmaps out from the last captured backbuffer until the video catches up while ( framePosition >= mNextFramePosition ) { mFrameGrabber->makeBitmap(); mNextFramePosition++; } } // Fetch bitmaps from the framegrabber and encode them GBitmap *bitmap = NULL; while ( (bitmap = mFrameGrabber->fetchBitmap()) != NULL ) { //mEncoder->pushProcessedBitmap(bitmap); if (!mEncoder->pushFrame(bitmap)) { Con::errorf("VideoCapture: an error occurred while encoding a frame. Recording aborted."); end(); break; } } // Garbage collect the processed bitmaps deleteProcessedBitmaps(); } void VideoCapture::registerEncoder( const char* name, VideoEncoderFactoryFn factoryFn ) { mEncoderFactoryFnList.increment(); mEncoderFactoryFnList.last().name = name; mEncoderFactoryFnList.last().factory = factoryFn; } bool VideoCapture::initEncoder( const char* name ) { if ( mEncoder ) { Con::errorf("VideoCapture:: cannot change video encoder while capturing! Stop the capture first!"); return false; } // Try creating an encoder based on the name for (U32 i=0; igetProcessedBitmap()) != NULL ) mBitmapDeleteList.push_back(bitmap); //Now delete them (or not... se below) while ( mBitmapDeleteList.size() ) { bitmap = mBitmapDeleteList[0]; mBitmapDeleteList.pop_front(); // Delete the bitmap only if it's the different than the next one (or it's the last one). // This is done because repeated frames re-use the same GBitmap object // and thus their pointers will appearl multiple times in the list if (mBitmapDeleteList.size() == 0 || bitmap != mBitmapDeleteList[0]) delete bitmap; } } ///---------------------------------------------------------------------- ///---------------------------------------------------------------------- void VideoEncoder::setFile( const char* path ) { mPath = path; } GBitmap* VideoEncoder::getProcessedBitmap() { GBitmap* bitmap = NULL; if (mProcessedBitmaps.tryPopFront(bitmap)) return bitmap; return NULL; } void VideoEncoder::pushProcessedBitmap( GBitmap* bitmap ) { mProcessedBitmaps.pushBack(bitmap); } ///---------------------------------------------------------------------- ///---------------------------------------------------------------------- GBitmap* VideoFrameGrabber::fetchBitmap() { if (mBitmapList.size() == 0) return NULL; GBitmap *bitmap = mBitmapList.first(); mBitmapList.pop_front(); return bitmap; } VideoFrameGrabber::VideoFrameGrabber() { GFXTextureManager::addEventDelegate( this, &VideoFrameGrabber::_onTextureEvent ); } VideoFrameGrabber::~VideoFrameGrabber() { GFXTextureManager::removeEventDelegate( this, &VideoFrameGrabber::_onTextureEvent ); } void VideoFrameGrabber::_onTextureEvent(GFXTexCallbackCode code) { if ( code == GFXZombify ) releaseTextures(); } ///---------------------------------------------------------------------- ///---------------------------------------------------------------------- //WLE - Vince //Changing the resolution to Point2I::Zero instead of the Point2I(0,0) better to use constants. DefineEngineFunction( startVideoCapture, void, ( GuiCanvas *canvas, const char *filename, const char *encoder, F32 framerate, Point2I resolution ), ( "THEORA", 30.0f, Point2I::Zero ), "Begins a video capture session.\n" "@see stopVideoCapture\n" "@ingroup Rendering\n" ) { #ifdef TORQUE_DEBUG Con::errorf("Recording video is disabled in debug!"); #else if ( !canvas ) { Con::errorf("startVideoCapture -Please specify a GuiCanvas object to record from!"); return; } VIDCAP->setFilename( filename ); VIDCAP->setEncoderName( encoder ); VIDCAP->setFramerate( framerate ); if ( !resolution.isZero() ) VIDCAP->setResolution(resolution); VIDCAP->begin(canvas); #endif } DefineEngineFunction( stopVideoCapture, void, (),, "Stops the video capture session.\n" "@see startVideoCapture\n" "@ingroup Rendering\n" ) { VIDCAP->end(); } DefineEngineFunction( playJournalToVideo, void, ( const char *journalFile, const char *videoFile, const char *encoder, F32 framerate, Point2I resolution ), ( nullAsType(), "THEORA", 30.0f, Point2I::Zero ), "Load a journal file and capture it video.\n" "@ingroup Rendering\n" ) { if ( !videoFile ) videoFile = journalFile; VIDCAP->setFilename( Torque::Path( videoFile ).getFileName() ); VIDCAP->setEncoderName( encoder ); VIDCAP->setFramerate( framerate ); if ( !resolution.isZero() ) VIDCAP->setResolution(resolution); VIDCAP->waitForCanvas(); Journal::Play( journalFile ); }