Mercurial > cortex
comparison org/capture-video.org @ 42:ecafe87ffddc
updating capture video with new examples
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Thu, 03 Nov 2011 16:01:14 -0700 |
parents | 97703c7f020e |
children | da4de661c5d9 |
comparison
equal
deleted
inserted
replaced
41:cce471a4108a | 42:ecafe87ffddc |
---|---|
141 The easiest way to achieve this special timing is to create a new | 141 The easiest way to achieve this special timing is to create a new |
142 timer that always reports the same framerate to JME3 every time it is | 142 timer that always reports the same framerate to JME3 every time it is |
143 called. | 143 called. |
144 | 144 |
145 | 145 |
146 =./jme3/src/core/com/jme3/system/IsoTimer.java= | 146 =./src/com/aurellem/capture/IsoTimer.java= |
147 #+include ./jme3/src/core/com/jme3/system/IsoTimer.java src java | 147 #+include ../../jmeCapture/src/com/aurellem/capture/IsoTimer.java src java |
148 | 148 |
149 If an Application uses this =IsoTimer= instead of the normal one, we | 149 If an Application uses this =IsoTimer= instead of the normal one, we |
150 can be sure that every call to =simpleUpdate=, for example, corresponds | 150 can be sure that every call to =simpleUpdate=, for example, corresponds |
151 to exactly $(\frac{1}{fps})$ seconds of game-time. | 151 to exactly $(\frac{1}{fps})$ seconds of game-time. |
152 | 152 |
153 In order to facilitate setting the =Timer= in user code, I added | |
154 getter and setter methods to =Application.java=. | |
155 | |
156 In =./jme3/src/core/com/jme3/app/Application.java= I added: | |
157 #+include ./jme3/src/core/com/jme3/app/Application.java src java :lines "340-356" | |
158 | |
159 * Encoding to Video | 153 * Encoding to Video |
160 | 154 |
161 Now that the issue of time is solved, we just need a function that | 155 Now that the issue of time is solved, we just need a function that |
162 writes each frame to a video. We can put this function somewhere | 156 writes each frame to a video. We can put this function somewhere |
163 where it will be called exactly one per frame. | 157 where it will be called exactly one per frame. |
164 | 158 |
159 The basic functions that a =VideoRecorder= should support are | |
160 recording, starting, stopping, and possibly a final finishing step | |
161 there it finilizes the recording (such as writing headers for a video | |
162 file). | |
163 | |
164 An appropiate interface describing this behaviour could look like | |
165 this: | |
166 | |
167 =./src/com/aurellem/capture/video/VideoRecorder.java= | |
168 #+include ../../jmeCapture/src/com/aurellem/capture/video/VideoRecorder.java src java | |
169 | |
170 | |
165 JME3 already provides exactly the class we need: the =SceneProcessor= | 171 JME3 already provides exactly the class we need: the =SceneProcessor= |
166 class can be attached to any viewport and the methods defined therein | 172 class can be attached to any viewport and the methods defined therein |
167 will be called at the appropriate points in the rendering process. | 173 will be called at the appropriate points in the rendering process. |
174 | |
175 However, it is also important to properly close the video stream and | |
176 write headers and such, and even though =SceneProcessor= has a | |
177 =.cleanup()= method, it is only called when the =SceneProcessor= is | |
178 removed from the =RenderManager=, not when the game is shutting down | |
179 when the user pressed ESC, for example. To obtain reliable shutdown | |
180 behaviour, we also have to implement =AppState=, which provides a | |
181 =.cleanup()= method that /is/ called on shutdown. | |
182 | |
183 Here is an AbstractVideoRecorder class that takes care of the details | |
184 of setup and teardown. | |
185 | |
186 =./src/com/aurellem/capture/video/AbstractVideoRecorder.java= | |
187 #+include ../../jmeCapture/src/com/aurellem/capture/video/AbstractVideoRecorder.java src java | |
168 | 188 |
169 If you want to generate video from Java, a great option is [[http://www.xuggle.com/][Xuggle]]. It | 189 If you want to generate video from Java, a great option is [[http://www.xuggle.com/][Xuggle]]. It |
170 takes care of everything related to video encoding and decoding and | 190 takes care of everything related to video encoding and decoding and |
171 runs on Windows, Linux and Mac. Out of all the video frameworks for | 191 runs on Windows, Linux and Mac. Out of all the video frameworks for |
172 Java I personally like this one the best. | 192 Java I personally like this one the best. |
173 | 193 |
174 Here is a =SceneProcessor= that uses [[http://www.xuggle.com/][Xuggle]] to write each frame to a | 194 Here is a =VideoRecorder= that uses [[http://www.xuggle.com/][Xuggle]] to write each frame to a |
175 video file. | 195 video file. |
176 | 196 |
177 =./jme3/src/core/com/jme3/app/VideoProcessor.java= | 197 =./src/com/aurellem/capture/video/XuggleVideoRecorder.java= |
178 #+include ./jme3/src/core/com/jme3/app/VideoProcessor.java src java | 198 #+include ../../jmeCapture/src/com/aurellem/capture/video/XuggleVideoRecorder.java src java |
179 | 199 |
180 With this, we are able to record video! | 200 With this, we are able to record video! |
201 | |
202 However, it can be hard to properly install Xuggle. For those of you | |
203 who would rather not use Xuggle, here is an alternate class that uses | |
204 [[http://www.randelshofer.ch/blog/2008/08/writing-avi-videos-in-pure-java/][Werner Randelshofer's]] excellent pure Java AVI file writer. | |
205 | |
206 =./src/com/aurellem/capture/video/AVIVideoRecorder.java= | |
207 #+include ../../jmeCapture/src/com/aurellem/capture/video/AVIVideoRecorder.java src java | |
208 | |
209 This =AVIVideoRecorder= is more limited than the | |
210 =XuggleVideoRecorder=, but requires less external dependencies. | |
211 | |
212 Finally, for those of you who prefer to create the final video from a | |
213 sequence of images, there is the =FileVideoRecorder=, which records | |
214 each frame to a folder as a sequentially numbered image file. Note | |
215 that you have to remember the FPS at which you recorded the video, as | |
216 this information is lost when saving each frame to a file. | |
217 | |
218 =./src/com/aurellem/capture/video/FileVideoRecorder.java= | |
219 #+include ../../jmeCapture/src/com/aurellem/capture/video/FileVideoRecorder.java src java | |
220 | |
221 | |
222 * /Really/ Simple Video Recording | |
223 | |
224 The most common case for recording a video is probably to just capture | |
225 whatever is on your screen exactly as you see it. In this case, this | |
226 method will do. | |
227 | |
228 #+begin_src java | |
229 public static void captureVideo(final Application app, | |
230 final File file) throws IOException{ | |
231 final AbstractVideoRecorder videoRecorder; | |
232 if (file.getCanonicalPath().endsWith(".avi")){ | |
233 videoRecorder = new AVIVideoRecorder(file);} | |
234 else if (file.isDirectory()){ | |
235 videoRecorder = new FileVideoRecorder(file);} | |
236 else { videoRecorder = new XuggleVideoRecorder(file);} | |
237 | |
238 Callable<Object> thunk = new Callable<Object>(){ | |
239 public Object call(){ | |
240 ViewPort viewPort = | |
241 app.getRenderManager() | |
242 .createPostView("aurellem record", app.getCamera()); | |
243 viewPort.setClearFlags(false, false, false); | |
244 // get GUI node stuff | |
245 for (Spatial s : app.getGuiViewPort().getScenes()){ | |
246 viewPort.attachScene(s); | |
247 } | |
248 app.getStateManager().attach(videoRecorder); | |
249 viewPort.addProcessor(videoRecorder); | |
250 return null; | |
251 } | |
252 }; | |
253 app.enqueue(thunk); | |
254 } | |
255 #+end_src | |
256 | |
257 This will select the appropiate backend =VideoRecorder= class | |
258 depending on the file name you specify, and insturment your | |
259 application to record video to the file. You should still set the | |
260 game's timer to an =IsoTimer= with the desired fps. | |
261 | |
262 This example will record video from the ocean scene from the | |
263 jMonkeyEngine test suite. | |
264 #+begin_src java | |
265 File video = File.createTempFile("JME-water-video", ".avi"); | |
266 captureVideo(app, video); | |
267 app.start(); | |
268 System.out.println(video.getCanonicalPath()); | |
269 #+end_src | |
270 | |
271 | |
272 I've added support for this under a class called | |
273 =com.aurellem.capture.Capture=. You can get it [[http://hg.bortreb.com/jmeCapture/][here]]. | |
274 | |
181 | 275 |
182 * Hello Video! | 276 * Hello Video! |
183 | 277 |
184 I've taken [[http://code.google.com/p/jmonkeyengine/source/browse/trunk/engine/src/test/jme3test/helloworld/HelloLoop.java][=./jme3/src/test/jme3test/helloworld/HelloLoop.java=]] and | 278 I've taken [[http://code.google.com/p/jmonkeyengine/source/browse/trunk/engine/src/test/jme3test/helloworld/HelloLoop.java][=./jme3/src/test/jme3test/helloworld/HelloLoop.java=]] and |
185 augmented it with video output as follows: | 279 augmented it with video output as follows: |
186 | 280 |
187 =./jme3/src/test/jme3test/helloworld/HelloVideo.java= | 281 =./src/com/aurellem/capture/examples/HelloVideo.java= |
188 #+include ./jme3/src/test/jme3test/helloworld/HelloVideo.java src java | 282 #+include ../../src/com/aurellem/capture/examples/HelloVideo.java src java |
189 | 283 |
190 The videos are created in the =hello-video= directory | 284 The videos are created in the =hello-video= directory |
191 | 285 |
192 #+begin_src sh :results verbatim | 286 #+begin_src sh :results verbatim :exports both |
193 du -h hello-video/* | 287 du -h hello-video/* |
194 #+end_src | 288 #+end_src |
195 | 289 |
196 #+results: | 290 #+results: |
197 : 932K hello-video/hello-video-moving.flv | 291 : 932K hello-video/hello-video-moving.flv |
214 </iframe> | 308 </iframe> |
215 | 309 |
216 #+END_HTML | 310 #+END_HTML |
217 | 311 |
218 | 312 |
219 | |
220 * Summary | 313 * Summary |
221 It's quite easy to augment your own application to record video, | 314 It's quite easy to augment your own application to record video, |
222 almost regardless of how complicated the actual application is. You | 315 almost regardless of how complicated the actual application is. You |
223 can also record from multiple ViewPorts as the above example shows. | 316 can also record from multiple ViewPorts as the above example shows. |
224 | 317 |
229 | 322 |
230 #+begin_src java :exports code | 323 #+begin_src java :exports code |
231 this.setTimer(new IsoTimer(30)); | 324 this.setTimer(new IsoTimer(30)); |
232 #+end_src | 325 #+end_src |
233 | 326 |
234 Somewhere in the initialization of your Application. Right now, you | 327 Somewhere in the initialization of your Application. |
235 will have to add the =setTimer= method to =Application=, but hopefully | |
236 this method will be included soon by the JMonkeyEngine3 team. | |
237 | |
238 Then, you create a =VideoProcessor= object and attach it to the | |
239 =ViewPort= from which you want to record. | |
240 | 328 |
241 If you want to record from the game's main =ViewPort= to a file called | 329 If you want to record from the game's main =ViewPort= to a file called |
242 =/home/r/record.flv=, then add: | 330 =/home/r/record.flv=, then add: |
243 | 331 |
244 #+begin_src java :exports code | 332 #+begin_src java :exports code |
245 viewPort.addProcessor(new VideoProcessor(new File("/home/r/record.flv"))); | 333 Capture.captureVideo(app, new File("/home/r/record.flv")); |
246 #+end_src | 334 #+end_src |
247 | 335 |
248 Do this for each =ViewPort= from which you want to record. The more | 336 Before you call =app.start()=; |
249 ViewPorts from which you record, the slower the game will run, but | |
250 this slowness will not affect the final video output. | |
251 | 337 |
252 * More Examples | 338 * More Examples |
253 ** Hello Physics | 339 ** COMMENT Hello Physics |
254 =HelloVideo= is boring. Let's add some video capturing to =HelloPhysics= | 340 =HelloVideo= is boring. Let's add some video capturing to =HelloPhysics= |
255 and create something fun! | 341 and create something fun! |
256 | 342 |
257 This example is a modified version of =HelloPhysics= that creates four | 343 This example is a modified version of =HelloPhysics= that creates four |
258 simultaneous views of the same scene of cannonballs careening into a | 344 simultaneous views of the same scene of cannonballs careening into a |
339 | 425 |
340 | 426 |
341 Thats a terribly large size! | 427 Thats a terribly large size! |
342 Let's compress it: | 428 Let's compress it: |
343 | 429 |
344 ** Compressing the HelloPhysics Video | 430 ** COMMENT Compressing the HelloPhysics Video |
345 First, we'll scale the video, then, we'll decrease it's bitrate. The | 431 First, we'll scale the video, then, we'll decrease it's bitrate. The |
346 end result will be perfect for upload to YouTube. | 432 end result will be perfect for upload to YouTube. |
347 | 433 |
348 #+begin_src sh :results silent | 434 #+begin_src sh :results silent |
349 cd youtube | 435 cd youtube |
449 JME3 Xuggle Aurellem video capture | 535 JME3 Xuggle Aurellem video capture |
450 | 536 |
451 | 537 |
452 * Sample Videos | 538 * Sample Videos |
453 I encoded most of the original JME3 Hello demos for your viewing | 539 I encoded most of the original JME3 Hello demos for your viewing |
454 pleasure, all using the =VideoProcessor= and =IsoTimer= classes. | 540 pleasure, all using the =Capture= and =IsoTimer= classes. |
455 | 541 |
456 ** HelloTerrain | 542 ** HelloTerrain |
457 [[http://youtu.be/5_4wyDFwrVQ][HelloTerrain.avi]] | 543 [[http://youtu.be/5_4wyDFwrVQ][HelloTerrain.avi]] |
458 | 544 |
459 #+begin_html | 545 #+begin_html |
649 | 735 |
650 | 736 |
651 | 737 |
652 | 738 |
653 | 739 |
740 |