2
0

videoCapture.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  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. mEncoder(NULL),
  47. mIsRecording(false),
  48. mCanvas(NULL),
  49. mFrameGrabber(NULL),
  50. mWaitingForCanvas(false),
  51. mResolution(0,0),
  52. mFrameRate(30.0f),
  53. mEncoderName("THEORA"),
  54. mFileName(""),
  55. mMsPerFrameError(0)
  56. {
  57. }
  58. S32 VideoCapture::getMsPerFrame()
  59. {
  60. //Add accumulated error to ms per frame before rounding
  61. F32 roundTime = mFloor(mMsPerFrame + mMsPerFrameError + 0.5f);
  62. //Accumulate the rounding errors
  63. mMsPerFrameError += mMsPerFrame - roundTime;
  64. return (S32)roundTime;
  65. }
  66. void VideoCapture::begin( GuiCanvas* canvas )
  67. {
  68. // No longer waiting for a canvas
  69. mWaitingForCanvas = false;
  70. // No specified file
  71. if (mFileName.isEmpty())
  72. {
  73. Con::errorf("VideoCapture: no file specified!");
  74. return;
  75. }
  76. // No framegrabber, cannot capture
  77. if (mFrameGrabber == NULL)
  78. {
  79. Con::errorf("VideoCapture: cannot capture without a VideoFrameGrabber! One should be created in the GFXDevice initialization!");
  80. return;
  81. }
  82. // Set the active encoder
  83. if (!initEncoder(mEncoderName))
  84. return;
  85. // Store the canvas, so we know which one to capture from
  86. mCanvas = canvas;
  87. // If the resolution is zero, get the current video mode
  88. if (mResolution.isZero())
  89. mResolution = mCanvas->getPlatformWindow()->getVideoMode().resolution;
  90. // Set the encoder file, framerate and resolution
  91. mEncoder->setFile(mFileName);
  92. mEncoder->setFramerate( &mFrameRate );
  93. mEncoder->setResolution( &mResolution );
  94. // The frame grabber must know about the resolution as well, since it'll do the resizing for us
  95. mFrameGrabber->setOutResolution( mResolution );
  96. // Calculate the ms per frame
  97. mMsPerFrame = 1000.0f / mFrameRate;
  98. // Start the encoder
  99. if (!mEncoder->begin())
  100. return;
  101. // We're now recording
  102. mIsRecording = true;
  103. mNextFramePosition = 0.0f;
  104. }
  105. void VideoCapture::end()
  106. {
  107. if (!mIsRecording)
  108. return;
  109. if (mEncoder && !mEncoder->end())
  110. Con::errorf("VideoCapture: an error has ocurred while closing the video stream");
  111. // Garbage collect the processed bitmaps
  112. deleteProcessedBitmaps();
  113. delete mEncoder;
  114. mEncoder = NULL;
  115. mIsRecording = false;
  116. }
  117. void VideoCapture::capture()
  118. {
  119. // If this is the first frame, capture and encode it right away
  120. if (mNextFramePosition == 0.0f)
  121. {
  122. mVideoCaptureStartTime = Platform::getVirtualMilliseconds();
  123. mCapturedFramePos = -1.0f;
  124. }
  125. // Calculate the frame position for this captured frame
  126. U32 frameTimeMs = Platform::getVirtualMilliseconds() - mVideoCaptureStartTime;
  127. F32 framePosition = (F32)frameTimeMs / mMsPerFrame;
  128. // Repeat until the current frame is captured
  129. while (framePosition > mCapturedFramePos)
  130. {
  131. // If the frame position is closer to the next frame position
  132. // than the previous one capture it
  133. if ( mFabs(framePosition - mNextFramePosition) < mFabs(mCapturedFramePos - mNextFramePosition) )
  134. {
  135. mFrameGrabber->captureBackBuffer();
  136. mCapturedFramePos = framePosition;
  137. }
  138. // If the new frame position is greater or equal than the next frame time
  139. // tell the framegrabber to make bitmaps out from the last captured backbuffer until the video catches up
  140. while ( framePosition >= mNextFramePosition )
  141. {
  142. mFrameGrabber->makeBitmap();
  143. mNextFramePosition++;
  144. }
  145. }
  146. // Fetch bitmaps from the framegrabber and encode them
  147. GBitmap *bitmap = NULL;
  148. while ( (bitmap = mFrameGrabber->fetchBitmap()) != NULL )
  149. {
  150. //mEncoder->pushProcessedBitmap(bitmap);
  151. if (!mEncoder->pushFrame(bitmap))
  152. {
  153. Con::errorf("VideoCapture: an error occurred while encoding a frame. Recording aborted.");
  154. end();
  155. break;
  156. }
  157. }
  158. // Garbage collect the processed bitmaps
  159. deleteProcessedBitmaps();
  160. }
  161. void VideoCapture::registerEncoder( const char* name, VideoEncoderFactoryFn factoryFn )
  162. {
  163. mEncoderFactoryFnList.increment();
  164. mEncoderFactoryFnList.last().name = name;
  165. mEncoderFactoryFnList.last().factory = factoryFn;
  166. }
  167. bool VideoCapture::initEncoder( const char* name )
  168. {
  169. if ( mEncoder )
  170. {
  171. Con::errorf("VideoCapture:: cannot change video encoder while capturing! Stop the capture first!");
  172. return false;
  173. }
  174. // Try creating an encoder based on the name
  175. for (U32 i=0; i<mEncoderFactoryFnList.size(); i++)
  176. {
  177. if (dStricmp(name, mEncoderFactoryFnList[i].name) == 0)
  178. {
  179. mEncoder = mEncoderFactoryFnList[i].factory();
  180. return true;
  181. }
  182. }
  183. //If we got here there's no encoder matching the speficied name
  184. Con::errorf("\"%s\" isn't a valid encoder!", name);
  185. return false;
  186. }
  187. void VideoCapture::deleteProcessedBitmaps()
  188. {
  189. if (mEncoder == NULL)
  190. return;
  191. //Grab bitmaps processed by our encoder
  192. GBitmap* bitmap = NULL;
  193. while ( (bitmap = mEncoder->getProcessedBitmap()) != NULL )
  194. mBitmapDeleteList.push_back(bitmap);
  195. //Now delete them (or not... se below)
  196. while ( mBitmapDeleteList.size() )
  197. {
  198. bitmap = mBitmapDeleteList[0];
  199. mBitmapDeleteList.pop_front();
  200. // Delete the bitmap only if it's the different than the next one (or it's the last one).
  201. // This is done because repeated frames re-use the same GBitmap object
  202. // and thus their pointers will appearl multiple times in the list
  203. if (mBitmapDeleteList.size() == 0 || bitmap != mBitmapDeleteList[0])
  204. delete bitmap;
  205. }
  206. }
  207. ///----------------------------------------------------------------------
  208. ///----------------------------------------------------------------------
  209. void VideoEncoder::setFile( const char* path )
  210. {
  211. mPath = path;
  212. }
  213. GBitmap* VideoEncoder::getProcessedBitmap()
  214. {
  215. GBitmap* bitmap = NULL;
  216. if (mProcessedBitmaps.tryPopFront(bitmap))
  217. return bitmap;
  218. return NULL;
  219. }
  220. void VideoEncoder::pushProcessedBitmap( GBitmap* bitmap )
  221. {
  222. mProcessedBitmaps.pushBack(bitmap);
  223. }
  224. ///----------------------------------------------------------------------
  225. ///----------------------------------------------------------------------
  226. GBitmap* VideoFrameGrabber::fetchBitmap()
  227. {
  228. if (mBitmapList.size() == 0)
  229. return NULL;
  230. GBitmap *bitmap = mBitmapList.first();
  231. mBitmapList.pop_front();
  232. return bitmap;
  233. }
  234. VideoFrameGrabber::VideoFrameGrabber()
  235. {
  236. GFXTextureManager::addEventDelegate( this, &VideoFrameGrabber::_onTextureEvent );
  237. }
  238. VideoFrameGrabber::~VideoFrameGrabber()
  239. {
  240. GFXTextureManager::removeEventDelegate( this, &VideoFrameGrabber::_onTextureEvent );
  241. }
  242. void VideoFrameGrabber::_onTextureEvent(GFXTexCallbackCode code)
  243. {
  244. if ( code == GFXZombify )
  245. releaseTextures();
  246. }
  247. ///----------------------------------------------------------------------
  248. ///----------------------------------------------------------------------
  249. //WLE - Vince
  250. //Changing the resolution to Point2I::Zero instead of the Point2I(0,0) better to use constants.
  251. DefineEngineFunction( startVideoCapture, void,
  252. ( GuiCanvas *canvas, const char *filename, const char *encoder, F32 framerate, Point2I resolution ),
  253. ( "THEORA", 30.0f, Point2I::Zero ),
  254. "Begins a video capture session.\n"
  255. "@see stopVideoCapture\n"
  256. "@ingroup Rendering\n" )
  257. {
  258. #ifdef TORQUE_DEBUG
  259. Con::errorf("Recording video is disabled in debug!");
  260. #else
  261. if ( !canvas )
  262. {
  263. Con::errorf("startVideoCapture -Please specify a GuiCanvas object to record from!");
  264. return;
  265. }
  266. VIDCAP->setFilename( filename );
  267. VIDCAP->setEncoderName( encoder );
  268. VIDCAP->setFramerate( framerate );
  269. if ( !resolution.isZero() )
  270. VIDCAP->setResolution(resolution);
  271. VIDCAP->begin(canvas);
  272. #endif
  273. }
  274. DefineEngineFunction( stopVideoCapture, void, (),,
  275. "Stops the video capture session.\n"
  276. "@see startVideoCapture\n"
  277. "@ingroup Rendering\n" )
  278. {
  279. VIDCAP->end();
  280. }
  281. DefineEngineFunction( playJournalToVideo, void,
  282. ( const char *journalFile, const char *videoFile, const char *encoder, F32 framerate, Point2I resolution ),
  283. ( nullAsType<const char*>(), "THEORA", 30.0f, Point2I::Zero ),
  284. "Load a journal file and capture it video.\n"
  285. "@ingroup Rendering\n" )
  286. {
  287. if ( !videoFile )
  288. videoFile = journalFile;
  289. VIDCAP->setFilename( Torque::Path( videoFile ).getFileName() );
  290. VIDCAP->setEncoderName( encoder );
  291. VIDCAP->setFramerate( framerate );
  292. if ( !resolution.isZero() )
  293. VIDCAP->setResolution(resolution);
  294. VIDCAP->waitForCanvas();
  295. Journal::Play( journalFile );
  296. }