annotate org/capture-video.org @ 213:319963720179

fleshing out vision
author Robert McIntyre <rlm@mit.edu>
date Thu, 09 Feb 2012 08:11:10 -0700
parents ecafe87ffddc
children da4de661c5d9
rev   line source
rlm@0 1 #+title: Capture Live Video Feeds from JMonkeyEngine
rlm@0 2 #+author: Robert McIntyre
rlm@0 3 #+email: rlm@mit.edu
rlm@0 4 #+description: Capture video from a JMonkeyEngine3 Application with Xuggle, and use gstreamer to compress the video to upload to YouTube.
rlm@0 5 #+keywords: JME3, video, Xuggle, JMonkeyEngine, youtube, capture video, Java
rlm@4 6 #+SETUPFILE: ../../aurellem/org/setup.org
rlm@4 7 #+INCLUDE: ../../aurellem/org/level-0.org
rlm@0 8
rlm@0 9
rlm@0 10 * The Problem
rlm@0 11 So you've made your cool new JMonkeyEngine3 game and you want to
rlm@0 12 create a demo video to show off your hard work. Screen capturing is
rlm@0 13 the most straightforward way to do this, but it can slow down your
rlm@0 14 game and produce low-quality video as a result. A better way is to
rlm@0 15 record a video feed directly from the game while it is
rlm@0 16 running.
rlm@0 17
rlm@0 18 In this post, I'll explain how you can alter your JMonkeyEngine3 game
rlm@0 19 to output video while it is running. The main trick is to alter the
rlm@0 20 pace of JMonkeyEngine3's in-game time: we allow the engine as much
rlm@0 21 time as it needs to compute complicated in-game events and to encode
rlm@0 22 video frames. As a result, the game appears to speed up and slow down
rlm@0 23 as the computational demands shift, but the end result is perfectly
rlm@0 24 smooth video output at a constant framerate.
rlm@0 25
rlm@0 26
rlm@0 27 * Game-time vs. User-time vs. Video-time
rlm@0 28
rlm@0 29 A standard JME3 application that extends =SimpleApplication= or
rlm@0 30 =Application= tries as hard as it can to keep in sync with
rlm@0 31 /user-time/. If a ball is rolling at 1 game-mile per game-hour in the
rlm@0 32 game, and you wait for one user-hour as measured by the clock on your
rlm@0 33 wall, then the ball should have traveled exactly one game-mile. In
rlm@0 34 order to keep sync with the real world, the game throttles its physics
rlm@0 35 engine and graphics display. If the computations involved in running
rlm@0 36 the game are too intense, then the game will first skip frames, then
rlm@0 37 sacrifice physics accuracy. If there are particuraly demanding
rlm@0 38 computations, then you may only get 1 fps, and the ball may tunnel
rlm@0 39 through the floor or obstacles due to inaccurate physics simulation,
rlm@0 40 but after the end of one user-hour, that ball will have traveled one
rlm@0 41 game-mile.
rlm@0 42
rlm@0 43 When we're recording video, we don't care if the game-time syncs with
rlm@0 44 user-time, but instead whether the time in the recorded video
rlm@0 45 (video-time) syncs with user-time. To continue the analogy, if we
rlm@0 46 recorded the ball rolling at 1 game-mile per game-hour and watched the
rlm@0 47 video later, we would want to see 30 fps video of the ball rolling at
rlm@0 48 1 video-mile per /user-hour/. It doesn't matter how much user-time it
rlm@0 49 took to simulate that hour of game-time to make the high-quality
rlm@0 50 recording.
rlm@0 51
rlm@0 52 * COMMENT Two examples to clarify the point:
rlm@0 53 ** Recording from a Simple Simulation
rlm@0 54
rlm@0 55 *** Without a Special Timer
rlm@0 56 You have a simulation of a ball rolling on an infinite empty plane at
rlm@0 57 one game-mile per game-hour, and a really good computer. Normally,
rlm@0 58 JME3 will throttle the physics engine and graphics display to sync the
rlm@0 59 game-time with user-time. If it takes one-thousandth of a second
rlm@0 60 user-time to simulate one-sixtieth of a second game time and another
rlm@0 61 one-thousandth of a second to draw to the screen, then JME3 will just
rlm@0 62 sit around for the remainder of $\frac{1}{60} - \frac{2}{1000}$
rlm@0 63 user-seconds, then calculate the next frame in $\frac{2}{1000}$
rlm@0 64 user-seconds, then wait, and so on. For every second of user time that
rlm@0 65 passes, one second of game-time passes, and the game will run at 60
rlm@0 66 frames per user-second.
rlm@0 67
rlm@0 68
rlm@0 69 *** With a Special Timer
rlm@0 70 Then, you change the game's timer so that user-time will be synced to
rlm@0 71 video-time. Assume that encoding a single frame takes 0 seconds
rlm@0 72 user-time to complete.
rlm@0 73
rlm@0 74 Now, JME3 takes advantage of all available resources. It still takes
rlm@0 75 one-thousandth of a second to calculate a physics tick, and another
rlm@0 76 one-thousandth to render to the screen. Then it takes 0 seconds to
rlm@0 77 write the video frame to disk and encode the video. In only one second
rlm@0 78 of user time, JME3 will complete 500 physics-tick/render/encode-video
rlm@0 79 cycles, and $\frac{500}{60}=8\frac{1}{3}$ seconds of game-time will
rlm@0 80 have passed. Game-time appears to dilate $8\frac{1}{3}\times$ with
rlm@0 81 respect to user-time, and in only 7.2 minutes user-time, one hour of
rlm@0 82 video will have been recorded. The game itself will run at 500 fps.
rlm@0 83 When someone watches the video, they will see 60 frames per
rlm@0 84 user-second, and $\frac{1}{60}$ video-seconds will pass each frame. It
rlm@0 85 will take exactly one hour user-time (and one hour video-time) for the
rlm@0 86 ball in the video to travel one video-mile.
rlm@0 87
rlm@0 88 ** Recording from a Complex Simulation
rlm@0 89
rlm@0 90 *** Without a Special Timer
rlm@0 91 You have a simulation of a ball rolling on an infinite empty plane at
rlm@0 92 one game-mile per game-hour accompanied by multiple explosions
rlm@0 93 involving thousands of nodes, particle effects, and complicated shadow
rlm@0 94 shaders to create realistic shadows. You also have a slow
rlm@0 95 laptop. Normally, JME3 must sacrifice rendering and physics simulation
rlm@0 96 to try to keep up. If it takes $\frac{1}{120}$ of a user-second to
rlm@0 97 calculate $\frac{1}{60}$ game-seconds, and an additional
rlm@0 98 $\frac{1}{60}$ of a user-second to render to screen, then JME3 has
rlm@0 99 it's work cut out for it. In order to render to the screen, it will
rlm@0 100 first step the game forward by up to four physics ticks before
rlm@0 101 rendering to the screen. If it still isn't fast enough then it will
rlm@0 102 decrease the accuracy of the physics engine until game-time and user
rlm@0 103 time are synched or a certain threshold is reached, at which point the
rlm@0 104 game visibly slows down. In this case, JME3 continuously repeat a
rlm@0 105 cycle of two physics ticks, and one screen render. For every
rlm@0 106 user-second that passes, one game-second will pass, but the game will
rlm@0 107 run at 30 fps instead of 60 fps like before.
rlm@0 108
rlm@0 109 *** With a Special Timer
rlm@0 110 Then, you change the game's timer so that user-time will be synced to
rlm@0 111 video-time. Once again, assume video encoding takes $\frac{1}{60}$ of
rlm@0 112 a user-second.
rlm@0 113
rlm@0 114 Now, JME3 will spend $\frac{1}{120}$ of a user-second to step the
rlm@0 115 physics tick $\frac{1}{60}$ game-seconds, $\frac{1}{60}$ to draw to
rlm@0 116 the screen, and an additional $\frac{1}{60}$ to encode the video and
rlm@0 117 write the frame to disk. This is a total of $\frac{1}{24}$
rlm@0 118 user-seconds for each $\frac{1}{60}$ game-seconds. It will take
rlm@0 119 $(\frac{60}{24} = 2.5)$ user-hours to record one game-hour and game-time
rlm@0 120 will appear to flow two-fifths as fast as user time while the game is
rlm@0 121 running. However, just as in example one, when all is said and done we
rlm@0 122 will have an hour long video at 60 fps.
rlm@0 123
rlm@0 124
rlm@0 125 * COMMENT proposed names for the new timer
rlm@0 126 # METRONOME
rlm@0 127 # IsoTimer
rlm@0 128 # EvenTimer
rlm@0 129 # PulseTimer
rlm@0 130 # FixedTimer
rlm@0 131 # RigidTimer
rlm@0 132 # FixedTempo
rlm@0 133 # RegularTimer
rlm@0 134 # MetronomeTimer
rlm@0 135 # ConstantTimer
rlm@0 136 # SteadyTimer
rlm@0 137
rlm@0 138
rlm@0 139 * =IsoTimer= records time like a metronome
rlm@0 140
rlm@0 141 The easiest way to achieve this special timing is to create a new
rlm@0 142 timer that always reports the same framerate to JME3 every time it is
rlm@0 143 called.
rlm@0 144
rlm@0 145
rlm@42 146 =./src/com/aurellem/capture/IsoTimer.java=
rlm@42 147 #+include ../../jmeCapture/src/com/aurellem/capture/IsoTimer.java src java
rlm@0 148
rlm@0 149 If an Application uses this =IsoTimer= instead of the normal one, we
rlm@0 150 can be sure that every call to =simpleUpdate=, for example, corresponds
rlm@0 151 to exactly $(\frac{1}{fps})$ seconds of game-time.
rlm@0 152
rlm@0 153 * Encoding to Video
rlm@0 154
rlm@0 155 Now that the issue of time is solved, we just need a function that
rlm@0 156 writes each frame to a video. We can put this function somewhere
rlm@0 157 where it will be called exactly one per frame.
rlm@0 158
rlm@42 159 The basic functions that a =VideoRecorder= should support are
rlm@42 160 recording, starting, stopping, and possibly a final finishing step
rlm@42 161 there it finilizes the recording (such as writing headers for a video
rlm@42 162 file).
rlm@42 163
rlm@42 164 An appropiate interface describing this behaviour could look like
rlm@42 165 this:
rlm@42 166
rlm@42 167 =./src/com/aurellem/capture/video/VideoRecorder.java=
rlm@42 168 #+include ../../jmeCapture/src/com/aurellem/capture/video/VideoRecorder.java src java
rlm@42 169
rlm@42 170
rlm@0 171 JME3 already provides exactly the class we need: the =SceneProcessor=
rlm@0 172 class can be attached to any viewport and the methods defined therein
rlm@0 173 will be called at the appropriate points in the rendering process.
rlm@0 174
rlm@42 175 However, it is also important to properly close the video stream and
rlm@42 176 write headers and such, and even though =SceneProcessor= has a
rlm@42 177 =.cleanup()= method, it is only called when the =SceneProcessor= is
rlm@42 178 removed from the =RenderManager=, not when the game is shutting down
rlm@42 179 when the user pressed ESC, for example. To obtain reliable shutdown
rlm@42 180 behaviour, we also have to implement =AppState=, which provides a
rlm@42 181 =.cleanup()= method that /is/ called on shutdown.
rlm@42 182
rlm@42 183 Here is an AbstractVideoRecorder class that takes care of the details
rlm@42 184 of setup and teardown.
rlm@42 185
rlm@42 186 =./src/com/aurellem/capture/video/AbstractVideoRecorder.java=
rlm@42 187 #+include ../../jmeCapture/src/com/aurellem/capture/video/AbstractVideoRecorder.java src java
rlm@42 188
rlm@0 189 If you want to generate video from Java, a great option is [[http://www.xuggle.com/][Xuggle]]. It
rlm@0 190 takes care of everything related to video encoding and decoding and
rlm@0 191 runs on Windows, Linux and Mac. Out of all the video frameworks for
rlm@0 192 Java I personally like this one the best.
rlm@0 193
rlm@42 194 Here is a =VideoRecorder= that uses [[http://www.xuggle.com/][Xuggle]] to write each frame to a
rlm@0 195 video file.
rlm@0 196
rlm@42 197 =./src/com/aurellem/capture/video/XuggleVideoRecorder.java=
rlm@42 198 #+include ../../jmeCapture/src/com/aurellem/capture/video/XuggleVideoRecorder.java src java
rlm@0 199
rlm@0 200 With this, we are able to record video!
rlm@0 201
rlm@42 202 However, it can be hard to properly install Xuggle. For those of you
rlm@42 203 who would rather not use Xuggle, here is an alternate class that uses
rlm@42 204 [[http://www.randelshofer.ch/blog/2008/08/writing-avi-videos-in-pure-java/][Werner Randelshofer's]] excellent pure Java AVI file writer.
rlm@42 205
rlm@42 206 =./src/com/aurellem/capture/video/AVIVideoRecorder.java=
rlm@42 207 #+include ../../jmeCapture/src/com/aurellem/capture/video/AVIVideoRecorder.java src java
rlm@42 208
rlm@42 209 This =AVIVideoRecorder= is more limited than the
rlm@42 210 =XuggleVideoRecorder=, but requires less external dependencies.
rlm@42 211
rlm@42 212 Finally, for those of you who prefer to create the final video from a
rlm@42 213 sequence of images, there is the =FileVideoRecorder=, which records
rlm@42 214 each frame to a folder as a sequentially numbered image file. Note
rlm@42 215 that you have to remember the FPS at which you recorded the video, as
rlm@42 216 this information is lost when saving each frame to a file.
rlm@42 217
rlm@42 218 =./src/com/aurellem/capture/video/FileVideoRecorder.java=
rlm@42 219 #+include ../../jmeCapture/src/com/aurellem/capture/video/FileVideoRecorder.java src java
rlm@42 220
rlm@42 221
rlm@42 222 * /Really/ Simple Video Recording
rlm@42 223
rlm@42 224 The most common case for recording a video is probably to just capture
rlm@42 225 whatever is on your screen exactly as you see it. In this case, this
rlm@42 226 method will do.
rlm@42 227
rlm@42 228 #+begin_src java
rlm@42 229 public static void captureVideo(final Application app,
rlm@42 230 final File file) throws IOException{
rlm@42 231 final AbstractVideoRecorder videoRecorder;
rlm@42 232 if (file.getCanonicalPath().endsWith(".avi")){
rlm@42 233 videoRecorder = new AVIVideoRecorder(file);}
rlm@42 234 else if (file.isDirectory()){
rlm@42 235 videoRecorder = new FileVideoRecorder(file);}
rlm@42 236 else { videoRecorder = new XuggleVideoRecorder(file);}
rlm@42 237
rlm@42 238 Callable<Object> thunk = new Callable<Object>(){
rlm@42 239 public Object call(){
rlm@42 240 ViewPort viewPort =
rlm@42 241 app.getRenderManager()
rlm@42 242 .createPostView("aurellem record", app.getCamera());
rlm@42 243 viewPort.setClearFlags(false, false, false);
rlm@42 244 // get GUI node stuff
rlm@42 245 for (Spatial s : app.getGuiViewPort().getScenes()){
rlm@42 246 viewPort.attachScene(s);
rlm@42 247 }
rlm@42 248 app.getStateManager().attach(videoRecorder);
rlm@42 249 viewPort.addProcessor(videoRecorder);
rlm@42 250 return null;
rlm@42 251 }
rlm@42 252 };
rlm@42 253 app.enqueue(thunk);
rlm@42 254 }
rlm@42 255 #+end_src
rlm@42 256
rlm@42 257 This will select the appropiate backend =VideoRecorder= class
rlm@42 258 depending on the file name you specify, and insturment your
rlm@42 259 application to record video to the file. You should still set the
rlm@42 260 game's timer to an =IsoTimer= with the desired fps.
rlm@42 261
rlm@42 262 This example will record video from the ocean scene from the
rlm@42 263 jMonkeyEngine test suite.
rlm@42 264 #+begin_src java
rlm@42 265 File video = File.createTempFile("JME-water-video", ".avi");
rlm@42 266 captureVideo(app, video);
rlm@42 267 app.start();
rlm@42 268 System.out.println(video.getCanonicalPath());
rlm@42 269 #+end_src
rlm@42 270
rlm@42 271
rlm@42 272 I've added support for this under a class called
rlm@42 273 =com.aurellem.capture.Capture=. You can get it [[http://hg.bortreb.com/jmeCapture/][here]].
rlm@42 274
rlm@42 275
rlm@0 276 * Hello Video!
rlm@0 277
rlm@0 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
rlm@0 279 augmented it with video output as follows:
rlm@0 280
rlm@42 281 =./src/com/aurellem/capture/examples/HelloVideo.java=
rlm@42 282 #+include ../../src/com/aurellem/capture/examples/HelloVideo.java src java
rlm@0 283
rlm@0 284 The videos are created in the =hello-video= directory
rlm@0 285
rlm@42 286 #+begin_src sh :results verbatim :exports both
rlm@0 287 du -h hello-video/*
rlm@0 288 #+end_src
rlm@0 289
rlm@0 290 #+results:
rlm@0 291 : 932K hello-video/hello-video-moving.flv
rlm@0 292 : 640K hello-video/hello-video-static.flv
rlm@0 293
rlm@0 294 And can be immediately uploaded to youtube
rlm@0 295
rlm@0 296 - [[http://www.youtube.com/watch?v=C8gxVAySaPg][hello-video-moving.flv]]
rlm@0 297 #+BEGIN_HTML
rlm@0 298 <iframe width="425" height="349"
rlm@0 299 src="http://www.youtube.com/embed/C8gxVAySaPg"
rlm@0 300 frameborder="0" allowfullscreen>
rlm@0 301 </iframe>
rlm@0 302 #+END_HTML
rlm@0 303 - [[http://www.youtube.com/watch?v=pHcFOtIS07Q][hello-video-static.flv]]
rlm@0 304 #+BEGIN_HTML
rlm@0 305 <iframe width="425" height="349"
rlm@0 306 src="http://www.youtube.com/embed/pHcFOtIS07Q"
rlm@0 307 frameborder="0" allowfullscreen>
rlm@0 308 </iframe>
rlm@0 309
rlm@0 310 #+END_HTML
rlm@0 311
rlm@0 312
rlm@0 313 * Summary
rlm@0 314 It's quite easy to augment your own application to record video,
rlm@0 315 almost regardless of how complicated the actual application is. You
rlm@0 316 can also record from multiple ViewPorts as the above example shows.
rlm@0 317
rlm@0 318 The process for adding video recording to your application is as
rlm@0 319 follows:
rlm@0 320
rlm@0 321 Assuming you want to record at 30 fps, add:
rlm@0 322
rlm@0 323 #+begin_src java :exports code
rlm@0 324 this.setTimer(new IsoTimer(30));
rlm@0 325 #+end_src
rlm@0 326
rlm@42 327 Somewhere in the initialization of your Application.
rlm@0 328
rlm@0 329 If you want to record from the game's main =ViewPort= to a file called
rlm@0 330 =/home/r/record.flv=, then add:
rlm@0 331
rlm@0 332 #+begin_src java :exports code
rlm@42 333 Capture.captureVideo(app, new File("/home/r/record.flv"));
rlm@0 334 #+end_src
rlm@0 335
rlm@42 336 Before you call =app.start()=;
rlm@0 337
rlm@0 338 * More Examples
rlm@42 339 ** COMMENT Hello Physics
rlm@0 340 =HelloVideo= is boring. Let's add some video capturing to =HelloPhysics=
rlm@0 341 and create something fun!
rlm@0 342
rlm@0 343 This example is a modified version of =HelloPhysics= that creates four
rlm@0 344 simultaneous views of the same scene of cannonballs careening into a
rlm@0 345 brick wall.
rlm@0 346
rlm@0 347 =./jme3/src/test/jme3test/helloworld/HelloPhysicsWithVideo.java=
rlm@0 348 #+include ./jme3/src/test/jme3test/helloworld/HelloPhysicsWithVideo.java src java
rlm@0 349
rlm@0 350 Running the program outputs four videos into the =./physics-videos=
rlm@0 351 directory.
rlm@0 352
rlm@0 353 #+begin_src sh :exports both :results verbatim
rlm@0 354 ls ./physics-videos | grep -
rlm@0 355 #+end_src
rlm@0 356
rlm@0 357 #+results:
rlm@0 358 : lower-left.flv
rlm@0 359 : lower-right.flv
rlm@0 360 : upper-left.flv
rlm@0 361 : upper-right.flv
rlm@0 362
rlm@0 363 The videos are fused together with the following =gstreamer= commands:
rlm@0 364
rlm@0 365 #+begin_src sh :results silent
rlm@0 366 cd physics-videos
rlm@0 367
rlm@0 368 gst-launch-0.10 \
rlm@0 369 filesrc location=./upper-right.flv ! decodebin ! \
rlm@0 370 videoscale ! ffmpegcolorspace ! \
rlm@0 371 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 372 videobox border-alpha=0 left=-640 ! \
rlm@0 373 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
rlm@0 374 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
rlm@0 375 jpegenc ! avimux ! filesink location=upper.flv \
rlm@0 376 \
rlm@0 377 filesrc location=./upper-left.flv ! decodebin ! \
rlm@0 378 videoscale ! ffmpegcolorspace ! \
rlm@0 379 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 380 videobox right=-640 ! mix.
rlm@0 381 #+end_src
rlm@0 382
rlm@0 383 #+begin_src sh :results silent
rlm@0 384 cd physics-videos
rlm@0 385
rlm@0 386 gst-launch-0.10 \
rlm@0 387 filesrc location=./lower-left.flv ! decodebin ! \
rlm@0 388 videoscale ! ffmpegcolorspace ! \
rlm@0 389 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 390 videobox border-alpha=0 left=-640 ! \
rlm@0 391 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
rlm@0 392 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
rlm@0 393 jpegenc ! avimux ! filesink location=lower.flv \
rlm@0 394 \
rlm@0 395 filesrc location=./lower-right.flv ! decodebin ! \
rlm@0 396 videoscale ! ffmpegcolorspace ! \
rlm@0 397 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 398 videobox right=-640 ! mix.
rlm@0 399 #+end_src
rlm@0 400
rlm@0 401 #+begin_src sh :results silent
rlm@0 402 cd physics-videos
rlm@0 403
rlm@0 404 gst-launch-0.10 \
rlm@0 405 filesrc location=./upper.flv ! decodebin ! \
rlm@0 406 videoscale ! ffmpegcolorspace ! \
rlm@0 407 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
rlm@0 408 videobox border-alpha=0 bottom=-480 ! \
rlm@0 409 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
rlm@0 410 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \
rlm@0 411 jpegenc ! avimux ! filesink location=../youtube/helloPhysics.flv \
rlm@0 412 \
rlm@0 413 filesrc location=./lower.flv ! decodebin ! \
rlm@0 414 videoscale ! ffmpegcolorspace ! \
rlm@0 415 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
rlm@0 416 videobox top=-480 ! mix.
rlm@0 417 #+end_src
rlm@0 418
rlm@0 419 #+begin_src sh :results verbatim
rlm@0 420 du -h youtube/helloPhysics.flv
rlm@0 421 #+end_src
rlm@0 422
rlm@0 423 #+results:
rlm@0 424 : 180M physics-videos/helloPhysics.flv
rlm@0 425
rlm@0 426
rlm@0 427 Thats a terribly large size!
rlm@0 428 Let's compress it:
rlm@0 429
rlm@42 430 ** COMMENT Compressing the HelloPhysics Video
rlm@0 431 First, we'll scale the video, then, we'll decrease it's bitrate. The
rlm@0 432 end result will be perfect for upload to YouTube.
rlm@0 433
rlm@0 434 #+begin_src sh :results silent
rlm@0 435 cd youtube
rlm@0 436
rlm@0 437 gst-launch-0.10 \
rlm@0 438 filesrc location=./helloPhysics.flv ! decodebin ! \
rlm@0 439 videoscale ! ffmpegcolorspace ! \
rlm@0 440 `: # the original size is 1280 by 960` \
rlm@0 441 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \
rlm@0 442 videoscale ! \
rlm@0 443 `: # here we scale the video down` \
rlm@0 444 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 445 `: # and here we limit the bitrate` \
rlm@0 446 theoraenc bitrate=1024 quality=30 ! \
rlm@0 447 oggmux ! progressreport update-freq=1 ! \
rlm@0 448 filesink location=./helloPhysics.ogg
rlm@0 449 #+end_src
rlm@0 450
rlm@0 451 #+begin_src sh :results verbatim
rlm@0 452 du -h youtube/helloPhysics.ogg
rlm@0 453 #+end_src
rlm@0 454
rlm@0 455 #+results:
rlm@0 456 : 13M youtube/helloPhysics.ogg
rlm@0 457
rlm@0 458 [[http://www.youtube.com/watch?v=WIJt9aRGusc][helloPhysics.ogg]]
rlm@0 459
rlm@0 460 #+begin_html
rlm@0 461 <iframe width="425" height="349"
rlm@0 462 src="http://www.youtube.com/embed/WIJt9aRGusc?hl=en&fs=1"
rlm@0 463 frameborder="0" allowfullscreen>
rlm@0 464 </iframe>
rlm@0 465 #+end_html
rlm@0 466
rlm@0 467
rlm@0 468 ** COMMENT failed attempts
rlm@0 469 Let's try the [[http://diracvideo.org/][Dirac]] video encoder.
rlm@0 470
rlm@0 471 #+begin_src sh :results verbatim
rlm@0 472 cd youtube
rlm@0 473 START=$(date +%s)
rlm@0 474 gst-launch-0.10 \
rlm@0 475 filesrc location=./helloPhysics.flv ! decodebin ! \
rlm@0 476 videoscale ! ffmpegcolorspace ! \
rlm@0 477 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \
rlm@0 478 schroenc ! filesink location=./helloPhysics.drc > /dev/null
rlm@0 479 echo `expr $(( $(date +%s) - $START))`
rlm@0 480 #+end_src
rlm@0 481
rlm@0 482
rlm@0 483 #+results:
rlm@0 484 : 142
rlm@0 485
rlm@0 486 That took 142 seconds. Let's see how it does compression-wise:
rlm@0 487
rlm@0 488 #+begin_src sh :results verbatim
rlm@0 489 du -h ./youtube/helloPhysics.drc
rlm@0 490 #+end_src
rlm@0 491
rlm@0 492 #+results:
rlm@0 493 : 22M ./physics-videos/helloPhysics.drc
rlm@0 494
rlm@0 495
rlm@0 496 #+begin_src sh :results verbatim
rlm@0 497 cd youtube
rlm@0 498 START=$(date +%s)
rlm@0 499 gst-launch-0.10 \
rlm@0 500 filesrc location=./helloPhysics.flv ! decodebin ! \
rlm@0 501 videoscale ! ffmpegcolorspace ! \
rlm@0 502 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \
rlm@0 503 theoraenc ! oggmux ! filesink location=./helloPhysics.ogg \
rlm@0 504 > /dev/null
rlm@0 505 echo `expr $(( $(date +%s) - $START))`
rlm@0 506 #+end_src
rlm@0 507
rlm@0 508 #+results:
rlm@0 509 : 123
rlm@0 510
rlm@0 511 #+begin_src sh :results verbatim
rlm@0 512 du -h youtube/helloPhysics.ogg
rlm@0 513 #+end_src
rlm@0 514
rlm@0 515 #+results:
rlm@0 516 : 59M physics-videos/helloPhysics.ogg
rlm@0 517
rlm@0 518
rlm@0 519 =*.drc= files can not be uploaded to YouTube, so I'll go for the
rlm@0 520 avi file.
rlm@0 521
rlm@0 522
rlm@0 523 ** COMMENT text for videos
rlm@0 524 Video output from JMonkeyEngine3 (www.jmonkeyengine.org/) using Xuggle
rlm@0 525 (www.xuggle.com/). Everything is explained at
rlm@0 526 http://aurellem.org/cortex/capture-video.html.
rlm@0 527
rlm@0 528
rlm@0 529 Video output from JMonkeyEngine3 (www.jmonkeyengine.org/) HelloPhysics
rlm@0 530 demo application using Xuggle (www.xuggle.com/). Everything is
rlm@0 531 explained at http://aurellem.org/cortex/capture-video.html. Here,
rlm@0 532 four points of view are simultaneously recorded and then glued
rlm@0 533 together later.
rlm@0 534
rlm@0 535 JME3 Xuggle Aurellem video capture
rlm@0 536
rlm@0 537
rlm@0 538 * Sample Videos
rlm@0 539 I encoded most of the original JME3 Hello demos for your viewing
rlm@42 540 pleasure, all using the =Capture= and =IsoTimer= classes.
rlm@0 541
rlm@0 542 ** HelloTerrain
rlm@0 543 [[http://youtu.be/5_4wyDFwrVQ][HelloTerrain.avi]]
rlm@0 544
rlm@0 545 #+begin_html
rlm@0 546 <iframe width="425" height="349"
rlm@0 547 src="http://www.youtube.com/embed/5_4wyDFwrVQ"
rlm@0 548 frameborder="0" allowfullscreen>
rlm@0 549 </iframe>
rlm@0 550 #+end_html
rlm@0 551
rlm@0 552 ** HelloAssets
rlm@0 553 [[http://www.youtube.com/watch?v=oGg-Q6k1BM4][HelloAssets.avi]]
rlm@0 554
rlm@0 555 #+begin_html
rlm@0 556 <iframe width="425" height="349"
rlm@0 557 src="http://www.youtube.com/embed/oGg-Q6k1BM4?hl=en&fs=1"
rlm@0 558 frameborder="0" allowfullscreen>
rlm@0 559 </iframe>
rlm@0 560 #+end_html
rlm@0 561
rlm@0 562 ** HelloEffects
rlm@0 563 [[http://www.youtube.com/watch?v=TuxlLMe53hA][HelloEffects]]
rlm@0 564
rlm@0 565 #+begin_html
rlm@0 566 <iframe width="425" height="349"
rlm@0 567 src="http://www.youtube.com/embed/TuxlLMe53hA?hl=en&fs=1"
rlm@0 568 frameborder="0" allowfullscreen>
rlm@0 569 </iframe>
rlm@0 570 #+end_html
rlm@0 571
rlm@0 572 ** HelloCollision
rlm@0 573 [[http://www.youtube.com/watch?v=GPlvJkiZfFw][HelloCollision.avi]]
rlm@0 574
rlm@0 575 #+begin_html
rlm@0 576 <iframe width="425" height="349"
rlm@0 577 src="http://www.youtube.com/embed/GPlvJkiZfFw?hl=en&fs=1"
rlm@0 578 frameborder="0" allowfullscreen>
rlm@0 579 </iframe>
rlm@0 580 #+end_html
rlm@0 581
rlm@0 582 ** HelloAnimation
rlm@0 583 [[http://www.youtube.com/watch?v=SDCfOSPYUkg][HelloAnimation.avi]]
rlm@0 584
rlm@0 585 #+begin_html
rlm@0 586 <iframe width="425" height="349"
rlm@0 587 src="http://www.youtube.com/embed/SDCfOSPYUkg?hl=en&fs=1"
rlm@0 588 frameborder="0" allowfullscreen>
rlm@0 589 </iframe>
rlm@0 590 #+end_html
rlm@0 591
rlm@0 592 ** HelloNode
rlm@0 593 [[http://www.youtube.com/watch?v=pL-0fR0-ilQ][HelloNode.avi]]
rlm@0 594
rlm@0 595 #+begin_html
rlm@0 596 <iframe width="425" height="349"
rlm@0 597 src="http://www.youtube.com/embed/pL-0fR0-ilQ?hl=en&fs=1"
rlm@0 598 frameborder="0" allowfullscreen>
rlm@0 599 </iframe>
rlm@0 600 #+end_html
rlm@0 601
rlm@0 602 ** HelloLoop
rlm@0 603 [[http://www.youtube.com/watch?v=mosZzzcdE5w][HelloLoop.avi]]
rlm@0 604
rlm@0 605 #+begin_html
rlm@0 606 <iframe width="425" height="349"
rlm@0 607 src="http://www.youtube.com/embed/mosZzzcdE5w?hl=en&fs=1"
rlm@0 608 frameborder="0" allowfullscreen>
rlm@0 609 </iframe>
rlm@0 610 #+end_html
rlm@0 611
rlm@0 612
rlm@0 613 *** COMMENT x-form the other stupid
rlm@0 614 progressreport update-freq=1
rlm@0 615
rlm@0 616 gst-launch-0.10 \
rlm@0 617 filesrc location=./helloPhy ! decodebin ! \
rlm@0 618 videoscale ! ffmpegcolorspace ! \
rlm@0 619 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \
rlm@0 620 x264enc ! avimux ! filesink location=helloPhysics.avi \
rlm@0 621
rlm@0 622
rlm@0 623 gst-launch-0.10 \
rlm@0 624 filesrc location=./HelloAnimationStatic.flv ! decodebin ! \
rlm@0 625 videoscale ! ffmpegcolorspace ! \
rlm@0 626 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 627 videobox border-alpha=0 left=-640 ! \
rlm@0 628 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
rlm@0 629 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
rlm@0 630 x264enc ! avimux ! progressreport update-freq=1 ! \
rlm@0 631 filesink location=../youtube/HelloAnimation.avi \
rlm@0 632 \
rlm@0 633 filesrc location=./HelloAnimationMotion.flv ! decodebin ! \
rlm@0 634 videoscale ! ffmpegcolorspace ! \
rlm@0 635 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 636 videobox right=-640 ! mix.
rlm@0 637
rlm@0 638 gst-launch-0.10 \
rlm@0 639 filesrc location=./HelloCollisionMotion.flv ! decodebin ! \
rlm@0 640 videoscale ! ffmpegcolorspace ! \
rlm@0 641 video/x-raw-yuv, width=800, height=600, framerate=25/1 ! \
rlm@0 642 x264enc bitrate=1024 ! avimux ! \
rlm@0 643 filesink location=../youtube/HelloCollision.avi
rlm@0 644
rlm@0 645 gst-launch-0.10 \
rlm@0 646 filesrc location=./HelloEffectsStatic.flv ! decodebin ! \
rlm@0 647 videoscale ! ffmpegcolorspace ! \
rlm@0 648 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 649 videobox border-alpha=0 left=-640 ! \
rlm@0 650 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
rlm@0 651 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
rlm@0 652 x264enc bitrate=1024 ! avimux ! progressreport update-freq=1 ! \
rlm@0 653 filesink location=../youtube/HelloEffects.avi \
rlm@0 654 \
rlm@0 655 filesrc location=./HelloEffectsMotion.flv ! decodebin ! \
rlm@0 656 videoscale ! ffmpegcolorspace ! \
rlm@0 657 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 658 videobox right=-640 ! mix.
rlm@0 659
rlm@0 660 gst-launch-0.10 \
rlm@0 661 filesrc location=./HelloTerrainMotion.flv ! decodebin ! \
rlm@0 662 videoscale ! ffmpegcolorspace ! \
rlm@0 663 video/x-raw-yuv, width=800, height=600, framerate=25/1 ! \
rlm@0 664 x264enc bitrate=1024 ! avimux ! \
rlm@0 665 filesink location=../youtube/HelloTerrain.avi
rlm@0 666
rlm@0 667
rlm@0 668 gst-launch-0.10 \
rlm@0 669 filesrc location=./HelloAssetsStatic.flv ! decodebin ! \
rlm@0 670 videoscale ! ffmpegcolorspace ! \
rlm@0 671 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 672 videobox border-alpha=0 left=-640 ! \
rlm@0 673 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
rlm@0 674 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
rlm@0 675 x264enc bitrate=1024 ! avimux ! progressreport update-freq=1 ! \
rlm@0 676 filesink location=../youtube/HelloAssets.avi \
rlm@0 677 \
rlm@0 678 filesrc location=./HelloAssetsMotion.flv ! decodebin ! \
rlm@0 679 videoscale ! ffmpegcolorspace ! \
rlm@0 680 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 681 videobox right=-640 ! mix.
rlm@0 682
rlm@0 683
rlm@0 684 gst-launch-0.10 \
rlm@0 685 filesrc location=./HelloNodeStatic.flv ! decodebin ! \
rlm@0 686 videoscale ! ffmpegcolorspace ! \
rlm@0 687 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 688 videobox border-alpha=0 left=-640 ! \
rlm@0 689 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
rlm@0 690 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
rlm@0 691 x264enc bitrate=1024 ! avimux ! progressreport update-freq=1 ! \
rlm@0 692 filesink location=../youtube/HelloNode.avi \
rlm@0 693 \
rlm@0 694 filesrc location=./HelloNodeMotion.flv ! decodebin ! \
rlm@0 695 videoscale ! ffmpegcolorspace ! \
rlm@0 696 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 697 videobox right=-640 ! mix.
rlm@0 698
rlm@0 699 gst-launch-0.10 \
rlm@0 700 filesrc location=./HelloLoopStatic.flv ! decodebin ! \
rlm@0 701 videoscale ! ffmpegcolorspace ! \
rlm@0 702 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 703 videobox border-alpha=0 left=-640 ! \
rlm@0 704 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
rlm@0 705 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
rlm@0 706 x264enc bitrate=1024 ! avimux ! progressreport update-freq=1 ! \
rlm@0 707 filesink location=../youtube/HelloLoop.avi \
rlm@0 708 \
rlm@0 709 filesrc location=./HelloLoopMotion.flv ! decodebin ! \
rlm@0 710 videoscale ! ffmpegcolorspace ! \
rlm@0 711 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
rlm@0 712 videobox right=-640 ! mix.
rlm@0 713
rlm@32 714
rlm@32 715
rlm@32 716
rlm@32 717
rlm@32 718
rlm@32 719
rlm@32 720
rlm@32 721
rlm@32 722
rlm@32 723
rlm@32 724
rlm@32 725
rlm@32 726
rlm@32 727
rlm@32 728
rlm@32 729
rlm@32 730
rlm@32 731
rlm@32 732
rlm@32 733
rlm@32 734
rlm@32 735
rlm@32 736
rlm@32 737
rlm@32 738
rlm@32 739
rlm@42 740