Torque3D Documentation / _generateds / videoCapture.cpp

videoCapture.cpp

Engine/source/gfx/video/videoCapture.cpp

More...

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