videoCapture.cpp
Engine/source/gfx/video/videoCapture.cpp
Public Variables
Public Functions
DefineEngineFunction(playJournalToVideo , void , (const char *journalFile, const char *videoFile, const char *encoder, F32 framerate, Point2I resolution) , (nullAsType< const char * >(), "THEORA", 30.0f, Point2I::Zero) , "Load <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> journal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> and capture it <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">video.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Rendering\n</a>" )
DefineEngineFunction(startVideoCapture , void , (GuiCanvas *canvas, const char *filename, const char *encoder, F32 framerate, Point2I resolution) , ("THEORA", 30.0f, Point2I::Zero) , "Begins <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> video capture <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">session.\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">stopVideoCapture\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Rendering\n</a>" )
DefineEngineFunction(stopVideoCapture , void , () , "Stops the video capture <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">session.\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">startVideoCapture\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Rendering\n</a>" )
Detailed Description
Public Variables
MODULE_END
MODULE_INIT
MODULE_SHUTDOWN
Public Functions
DefineEngineFunction(playJournalToVideo , void , (const char *journalFile, const char *videoFile, const char *encoder, F32 framerate, Point2I resolution) , (nullAsType< const char * >(), "THEORA", 30.0f, Point2I::Zero) , "Load <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> journal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> and capture it <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">video.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Rendering\n</a>" )
DefineEngineFunction(startVideoCapture , void , (GuiCanvas *canvas, const char *filename, const char *encoder, F32 framerate, Point2I resolution) , ("THEORA", 30.0f, Point2I::Zero) , "Begins <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> video capture <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">session.\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">stopVideoCapture\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Rendering\n</a>" )
--------------------------------- ---------------------------------
DefineEngineFunction(stopVideoCapture , void , () , "Stops the video capture <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">session.\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">startVideoCapture\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Rendering\n</a>" )
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "platform/platform.h" 25#include "gfx/video/videoCapture.h" 26 27#include "console/console.h" 28#include "core/strings/stringFunctions.h" 29#include "core/util/journal/journal.h" 30#include "core/module.h" 31#include "gui/core/guiCanvas.h" 32#include "gfx/gfxTextureManager.h" 33#include "console/engineAPI.h" 34 35 36Vector<VideoCapture::EncoderFactory> VideoCapture::mEncoderFactoryFnList; 37 38MODULE_BEGIN( VideoCapture ) 39 40 MODULE_INIT_BEFORE( GFX ) 41 MODULE_SHUTDOWN_BEFORE( GFX ) 42 43 MODULE_INIT 44 { 45 ManagedSingleton< VideoCapture >::createSingleton(); 46 } 47 48 MODULE_SHUTDOWN 49 { 50 VIDCAP->end(); 51 ManagedSingleton< VideoCapture >::deleteSingleton(); 52 } 53 54MODULE_END; 55 56VideoCapture::VideoCapture() : 57 mCapturedFramePos(-1.0f), 58 mEncoder(NULL), 59 mFrameGrabber(NULL), 60 mCanvas(NULL), 61 mIsRecording(false), 62 mVideoCaptureStartTime(0), 63 mNextFramePosition(0.0f), 64 mFrameRate(30.0f), 65 mMsPerFrame(1000.0f / mFrameRate), 66 mResolution(0,0), 67 mWaitingForCanvas(false), 68 mEncoderName("THEORA"), 69 mMsPerFrameError(0), 70 mFileName("") 71{ 72} 73 74S32 VideoCapture::getMsPerFrame() 75{ 76 //Add accumulated error to ms per frame before rounding 77 F32 roundTime = mFloor(mMsPerFrame + mMsPerFrameError + 0.5f); 78 79 //Accumulate the rounding errors 80 mMsPerFrameError += mMsPerFrame - roundTime; 81 82 return (S32)roundTime; 83} 84 85void VideoCapture::begin( GuiCanvas* canvas ) 86{ 87 // No longer waiting for a canvas 88 mWaitingForCanvas = false; 89 90 // No specified file 91 if (mFileName.isEmpty()) 92 { 93 Con::errorf("VideoCapture: no file specified!"); 94 return; 95 } 96 97 // No framegrabber, cannot capture 98 if (mFrameGrabber == NULL) 99 { 100 Con::errorf("VideoCapture: cannot capture without a VideoFrameGrabber! One should be created in the GFXDevice initialization!"); 101 return; 102 } 103 104 // Set the active encoder 105 if (!initEncoder(mEncoderName)) 106 return; 107 108 // Store the canvas, so we know which one to capture from 109 mCanvas = canvas; 110 111 // If the resolution is zero, get the current video mode 112 if (mResolution.isZero()) 113 mResolution = mCanvas->getPlatformWindow()->getVideoMode().resolution; 114 115 // Set the encoder file, framerate and resolution 116 mEncoder->setFile(mFileName); 117 mEncoder->setFramerate( &mFrameRate ); 118 mEncoder->setResolution( &mResolution ); 119 120 // The frame grabber must know about the resolution as well, since it'll do the resizing for us 121 mFrameGrabber->setOutResolution( mResolution ); 122 123 // Calculate the ms per frame 124 mMsPerFrame = 1000.0f / mFrameRate; 125 126 // Start the encoder 127 if (!mEncoder->begin()) 128 return; 129 130 // We're now recording 131 mIsRecording = true; 132 mNextFramePosition = 0.0f; 133} 134 135void VideoCapture::end() 136{ 137 if (!mIsRecording) 138 return; 139 140 if (mEncoder && !mEncoder->end()) 141 Con::errorf("VideoCapture: an error has ocurred while closing the video stream"); 142 143 // Garbage collect the processed bitmaps 144 deleteProcessedBitmaps(); 145 146 delete mEncoder; 147 mEncoder = NULL; 148 149 mIsRecording = false; 150} 151 152void VideoCapture::capture() 153{ 154 // If this is the first frame, capture and encode it right away 155 if (mNextFramePosition == 0.0f) 156 { 157 mVideoCaptureStartTime = Platform::getVirtualMilliseconds(); 158 mCapturedFramePos = -1.0f; 159 } 160 161 // Calculate the frame position for this captured frame 162 U32 frameTimeMs = Platform::getVirtualMilliseconds() - mVideoCaptureStartTime; 163 F32 framePosition = (F32)frameTimeMs / mMsPerFrame; 164 165 // Repeat until the current frame is captured 166 while (framePosition > mCapturedFramePos) 167 { 168 // If the frame position is closer to the next frame position 169 // than the previous one capture it 170 if ( mFabs(framePosition - mNextFramePosition) < mFabs(mCapturedFramePos - mNextFramePosition) ) 171 { 172 mFrameGrabber->captureBackBuffer(); 173 mCapturedFramePos = framePosition; 174 } 175 176 // If the new frame position is greater or equal than the next frame time 177 // tell the framegrabber to make bitmaps out from the last captured backbuffer until the video catches up 178 while ( framePosition >= mNextFramePosition ) 179 { 180 mFrameGrabber->makeBitmap(); 181 mNextFramePosition++; 182 } 183 } 184 185 // Fetch bitmaps from the framegrabber and encode them 186 GBitmap *bitmap = NULL; 187 while ( (bitmap = mFrameGrabber->fetchBitmap()) != NULL ) 188 { 189 //mEncoder->pushProcessedBitmap(bitmap); 190 if (!mEncoder->pushFrame(bitmap)) 191 { 192 Con::errorf("VideoCapture: an error occurred while encoding a frame. Recording aborted."); 193 end(); 194 break; 195 } 196 } 197 198 // Garbage collect the processed bitmaps 199 deleteProcessedBitmaps(); 200} 201 202 203void VideoCapture::registerEncoder( const char* name, VideoEncoderFactoryFn factoryFn ) 204{ 205 mEncoderFactoryFnList.increment(); 206 mEncoderFactoryFnList.last().name = name; 207 mEncoderFactoryFnList.last().factory = factoryFn; 208} 209 210 211bool VideoCapture::initEncoder( const char* name ) 212{ 213 if ( mEncoder ) 214 { 215 Con::errorf("VideoCapture:: cannot change video encoder while capturing! Stop the capture first!"); 216 return false; 217 } 218 219 // Try creating an encoder based on the name 220 for (U32 i=0; i<mEncoderFactoryFnList.size(); i++) 221 { 222 if (dStricmp(name, mEncoderFactoryFnList[i].name) == 0) 223 { 224 mEncoder = mEncoderFactoryFnList[i].factory(); 225 return true; 226 } 227 } 228 229 //If we got here there's no encoder matching the speficied name 230 Con::errorf("\"%s\" isn't a valid encoder!", name); 231 return false; 232} 233 234void VideoCapture::deleteProcessedBitmaps() 235{ 236 if (mEncoder == NULL) 237 return; 238 239 //Grab bitmaps processed by our encoder 240 GBitmap* bitmap = NULL; 241 while ( (bitmap = mEncoder->getProcessedBitmap()) != NULL ) 242 mBitmapDeleteList.push_back(bitmap); 243 244 //Now delete them (or not... se below) 245 while ( mBitmapDeleteList.size() ) 246 { 247 bitmap = mBitmapDeleteList[0]; 248 mBitmapDeleteList.pop_front(); 249 250 // Delete the bitmap only if it's the different than the next one (or it's the last one). 251 // This is done because repeated frames re-use the same GBitmap object 252 // and thus their pointers will appearl multiple times in the list 253 if (mBitmapDeleteList.size() == 0 || bitmap != mBitmapDeleteList[0]) 254 delete bitmap; 255 } 256} 257 258///---------------------------------------------------------------------- 259 260///---------------------------------------------------------------------- 261 262void VideoEncoder::setFile( const char* path ) 263{ 264 mPath = path; 265} 266 267GBitmap* VideoEncoder::getProcessedBitmap() 268{ 269 GBitmap* bitmap = NULL; 270 if (mProcessedBitmaps.tryPopFront(bitmap)) 271 return bitmap; 272 return NULL; 273} 274 275void VideoEncoder::pushProcessedBitmap( GBitmap* bitmap ) 276{ 277 mProcessedBitmaps.pushBack(bitmap); 278} 279 280///---------------------------------------------------------------------- 281 282///---------------------------------------------------------------------- 283 284GBitmap* VideoFrameGrabber::fetchBitmap() 285{ 286 if (mBitmapList.size() == 0) 287 return NULL; 288 289 GBitmap *bitmap = mBitmapList.first(); 290 mBitmapList.pop_front(); 291 return bitmap; 292} 293 294VideoFrameGrabber::VideoFrameGrabber() 295{ 296 GFXTextureManager::addEventDelegate( this, &VideoFrameGrabber::_onTextureEvent ); 297} 298 299VideoFrameGrabber::~VideoFrameGrabber() 300{ 301 GFXTextureManager::removeEventDelegate( this, &VideoFrameGrabber::_onTextureEvent ); 302} 303 304void VideoFrameGrabber::_onTextureEvent(GFXTexCallbackCode code) 305{ 306 if ( code == GFXZombify ) 307 releaseTextures(); 308} 309 310///---------------------------------------------------------------------- 311 312///---------------------------------------------------------------------- 313//WLE - Vince 314//Changing the resolution to Point2I::Zero instead of the Point2I(0,0) better to use constants. 315DefineEngineFunction( startVideoCapture, void, 316 ( GuiCanvas *canvas, const char *filename, const char *encoder, F32 framerate, Point2I resolution ), 317 ( "THEORA", 30.0f, Point2I::Zero ), 318 "Begins a video capture session.\n" 319 "@see stopVideoCapture\n" 320 "@ingroup Rendering\n" ) 321{ 322#ifdef TORQUE_DEBUG 323 Con::errorf("Recording video is disabled in debug!"); 324#else 325 if ( !canvas ) 326 { 327 Con::errorf("startVideoCapture -Please specify a GuiCanvas object to record from!"); 328 return; 329 } 330 331 VIDCAP->setFilename( filename ); 332 VIDCAP->setEncoderName( encoder ); 333 VIDCAP->setFramerate( framerate ); 334 335 if ( !resolution.isZero() ) 336 VIDCAP->setResolution(resolution); 337 338 VIDCAP->begin(canvas); 339#endif 340} 341 342DefineEngineFunction( stopVideoCapture, void, (),, 343 "Stops the video capture session.\n" 344 "@see startVideoCapture\n" 345 "@ingroup Rendering\n" ) 346{ 347 VIDCAP->end(); 348} 349 350DefineEngineFunction( playJournalToVideo, void, 351 ( const char *journalFile, const char *videoFile, const char *encoder, F32 framerate, Point2I resolution ), 352 ( nullAsType<const char*>(), "THEORA", 30.0f, Point2I::Zero ), 353 "Load a journal file and capture it video.\n" 354 "@ingroup Rendering\n" ) 355{ 356 if ( !videoFile ) 357 videoFile = journalFile; 358 359 VIDCAP->setFilename( Torque::Path( videoFile ).getFileName() ); 360 VIDCAP->setEncoderName( encoder ); 361 VIDCAP->setFramerate( framerate ); 362 363 if ( !resolution.isZero() ) 364 VIDCAP->setResolution(resolution); 365 366 VIDCAP->waitForCanvas(); 367 368 Journal::Play( journalFile ); 369} 370