videoCapture.cpp 10 KB

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