Mercurial > cortex
view org/capture-video.org @ 32:97703c7f020e
updated org files
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Thu, 27 Oct 2011 23:41:14 -0700 |
parents | 50c92af2018e |
children | ecafe87ffddc |
line wrap: on
line source
1 #+title: Capture Live Video Feeds from JMonkeyEngine2 #+author: Robert McIntyre3 #+email: rlm@mit.edu4 #+description: Capture video from a JMonkeyEngine3 Application with Xuggle, and use gstreamer to compress the video to upload to YouTube.5 #+keywords: JME3, video, Xuggle, JMonkeyEngine, youtube, capture video, Java6 #+SETUPFILE: ../../aurellem/org/setup.org7 #+INCLUDE: ../../aurellem/org/level-0.org10 * The Problem11 So you've made your cool new JMonkeyEngine3 game and you want to12 create a demo video to show off your hard work. Screen capturing is13 the most straightforward way to do this, but it can slow down your14 game and produce low-quality video as a result. A better way is to15 record a video feed directly from the game while it is16 running.18 In this post, I'll explain how you can alter your JMonkeyEngine3 game19 to output video while it is running. The main trick is to alter the20 pace of JMonkeyEngine3's in-game time: we allow the engine as much21 time as it needs to compute complicated in-game events and to encode22 video frames. As a result, the game appears to speed up and slow down23 as the computational demands shift, but the end result is perfectly24 smooth video output at a constant framerate.27 * Game-time vs. User-time vs. Video-time29 A standard JME3 application that extends =SimpleApplication= or30 =Application= tries as hard as it can to keep in sync with31 /user-time/. If a ball is rolling at 1 game-mile per game-hour in the32 game, and you wait for one user-hour as measured by the clock on your33 wall, then the ball should have traveled exactly one game-mile. In34 order to keep sync with the real world, the game throttles its physics35 engine and graphics display. If the computations involved in running36 the game are too intense, then the game will first skip frames, then37 sacrifice physics accuracy. If there are particuraly demanding38 computations, then you may only get 1 fps, and the ball may tunnel39 through the floor or obstacles due to inaccurate physics simulation,40 but after the end of one user-hour, that ball will have traveled one41 game-mile.43 When we're recording video, we don't care if the game-time syncs with44 user-time, but instead whether the time in the recorded video45 (video-time) syncs with user-time. To continue the analogy, if we46 recorded the ball rolling at 1 game-mile per game-hour and watched the47 video later, we would want to see 30 fps video of the ball rolling at48 1 video-mile per /user-hour/. It doesn't matter how much user-time it49 took to simulate that hour of game-time to make the high-quality50 recording.52 * COMMENT Two examples to clarify the point:53 ** Recording from a Simple Simulation55 *** Without a Special Timer56 You have a simulation of a ball rolling on an infinite empty plane at57 one game-mile per game-hour, and a really good computer. Normally,58 JME3 will throttle the physics engine and graphics display to sync the59 game-time with user-time. If it takes one-thousandth of a second60 user-time to simulate one-sixtieth of a second game time and another61 one-thousandth of a second to draw to the screen, then JME3 will just62 sit around for the remainder of $\frac{1}{60} - \frac{2}{1000}$63 user-seconds, then calculate the next frame in $\frac{2}{1000}$64 user-seconds, then wait, and so on. For every second of user time that65 passes, one second of game-time passes, and the game will run at 6066 frames per user-second.69 *** With a Special Timer70 Then, you change the game's timer so that user-time will be synced to71 video-time. Assume that encoding a single frame takes 0 seconds72 user-time to complete.74 Now, JME3 takes advantage of all available resources. It still takes75 one-thousandth of a second to calculate a physics tick, and another76 one-thousandth to render to the screen. Then it takes 0 seconds to77 write the video frame to disk and encode the video. In only one second78 of user time, JME3 will complete 500 physics-tick/render/encode-video79 cycles, and $\frac{500}{60}=8\frac{1}{3}$ seconds of game-time will80 have passed. Game-time appears to dilate $8\frac{1}{3}\times$ with81 respect to user-time, and in only 7.2 minutes user-time, one hour of82 video will have been recorded. The game itself will run at 500 fps.83 When someone watches the video, they will see 60 frames per84 user-second, and $\frac{1}{60}$ video-seconds will pass each frame. It85 will take exactly one hour user-time (and one hour video-time) for the86 ball in the video to travel one video-mile.88 ** Recording from a Complex Simulation90 *** Without a Special Timer91 You have a simulation of a ball rolling on an infinite empty plane at92 one game-mile per game-hour accompanied by multiple explosions93 involving thousands of nodes, particle effects, and complicated shadow94 shaders to create realistic shadows. You also have a slow95 laptop. Normally, JME3 must sacrifice rendering and physics simulation96 to try to keep up. If it takes $\frac{1}{120}$ of a user-second to97 calculate $\frac{1}{60}$ game-seconds, and an additional98 $\frac{1}{60}$ of a user-second to render to screen, then JME3 has99 it's work cut out for it. In order to render to the screen, it will100 first step the game forward by up to four physics ticks before101 rendering to the screen. If it still isn't fast enough then it will102 decrease the accuracy of the physics engine until game-time and user103 time are synched or a certain threshold is reached, at which point the104 game visibly slows down. In this case, JME3 continuously repeat a105 cycle of two physics ticks, and one screen render. For every106 user-second that passes, one game-second will pass, but the game will107 run at 30 fps instead of 60 fps like before.109 *** With a Special Timer110 Then, you change the game's timer so that user-time will be synced to111 video-time. Once again, assume video encoding takes $\frac{1}{60}$ of112 a user-second.114 Now, JME3 will spend $\frac{1}{120}$ of a user-second to step the115 physics tick $\frac{1}{60}$ game-seconds, $\frac{1}{60}$ to draw to116 the screen, and an additional $\frac{1}{60}$ to encode the video and117 write the frame to disk. This is a total of $\frac{1}{24}$118 user-seconds for each $\frac{1}{60}$ game-seconds. It will take119 $(\frac{60}{24} = 2.5)$ user-hours to record one game-hour and game-time120 will appear to flow two-fifths as fast as user time while the game is121 running. However, just as in example one, when all is said and done we122 will have an hour long video at 60 fps.125 * COMMENT proposed names for the new timer126 # METRONOME127 # IsoTimer128 # EvenTimer129 # PulseTimer130 # FixedTimer131 # RigidTimer132 # FixedTempo133 # RegularTimer134 # MetronomeTimer135 # ConstantTimer136 # SteadyTimer139 * =IsoTimer= records time like a metronome141 The easiest way to achieve this special timing is to create a new142 timer that always reports the same framerate to JME3 every time it is143 called.146 =./jme3/src/core/com/jme3/system/IsoTimer.java=147 #+include ./jme3/src/core/com/jme3/system/IsoTimer.java src java149 If an Application uses this =IsoTimer= instead of the normal one, we150 can be sure that every call to =simpleUpdate=, for example, corresponds151 to exactly $(\frac{1}{fps})$ seconds of game-time.153 In order to facilitate setting the =Timer= in user code, I added154 getter and setter methods to =Application.java=.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"159 * Encoding to Video161 Now that the issue of time is solved, we just need a function that162 writes each frame to a video. We can put this function somewhere163 where it will be called exactly one per frame.165 JME3 already provides exactly the class we need: the =SceneProcessor=166 class can be attached to any viewport and the methods defined therein167 will be called at the appropriate points in the rendering process.169 If you want to generate video from Java, a great option is [[http://www.xuggle.com/][Xuggle]]. It170 takes care of everything related to video encoding and decoding and171 runs on Windows, Linux and Mac. Out of all the video frameworks for172 Java I personally like this one the best.174 Here is a =SceneProcessor= that uses [[http://www.xuggle.com/][Xuggle]] to write each frame to a175 video file.177 =./jme3/src/core/com/jme3/app/VideoProcessor.java=178 #+include ./jme3/src/core/com/jme3/app/VideoProcessor.java src java180 With this, we are able to record video!182 * Hello Video!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=]] and185 augmented it with video output as follows:187 =./jme3/src/test/jme3test/helloworld/HelloVideo.java=188 #+include ./jme3/src/test/jme3test/helloworld/HelloVideo.java src java190 The videos are created in the =hello-video= directory192 #+begin_src sh :results verbatim193 du -h hello-video/*194 #+end_src196 #+results:197 : 932K hello-video/hello-video-moving.flv198 : 640K hello-video/hello-video-static.flv200 And can be immediately uploaded to youtube202 - [[http://www.youtube.com/watch?v=C8gxVAySaPg][hello-video-moving.flv]]203 #+BEGIN_HTML204 <iframe width="425" height="349"205 src="http://www.youtube.com/embed/C8gxVAySaPg"206 frameborder="0" allowfullscreen>207 </iframe>208 #+END_HTML209 - [[http://www.youtube.com/watch?v=pHcFOtIS07Q][hello-video-static.flv]]210 #+BEGIN_HTML211 <iframe width="425" height="349"212 src="http://www.youtube.com/embed/pHcFOtIS07Q"213 frameborder="0" allowfullscreen>214 </iframe>216 #+END_HTML220 * Summary221 It's quite easy to augment your own application to record video,222 almost regardless of how complicated the actual application is. You223 can also record from multiple ViewPorts as the above example shows.225 The process for adding video recording to your application is as226 follows:228 Assuming you want to record at 30 fps, add:230 #+begin_src java :exports code231 this.setTimer(new IsoTimer(30));232 #+end_src234 Somewhere in the initialization of your Application. Right now, you235 will have to add the =setTimer= method to =Application=, but hopefully236 this method will be included soon by the JMonkeyEngine3 team.238 Then, you create a =VideoProcessor= object and attach it to the239 =ViewPort= from which you want to record.241 If you want to record from the game's main =ViewPort= to a file called242 =/home/r/record.flv=, then add:244 #+begin_src java :exports code245 viewPort.addProcessor(new VideoProcessor(new File("/home/r/record.flv")));246 #+end_src248 Do this for each =ViewPort= from which you want to record. The more249 ViewPorts from which you record, the slower the game will run, but250 this slowness will not affect the final video output.252 * More Examples253 ** Hello Physics254 =HelloVideo= is boring. Let's add some video capturing to =HelloPhysics=255 and create something fun!257 This example is a modified version of =HelloPhysics= that creates four258 simultaneous views of the same scene of cannonballs careening into a259 brick wall.261 =./jme3/src/test/jme3test/helloworld/HelloPhysicsWithVideo.java=262 #+include ./jme3/src/test/jme3test/helloworld/HelloPhysicsWithVideo.java src java264 Running the program outputs four videos into the =./physics-videos=265 directory.267 #+begin_src sh :exports both :results verbatim268 ls ./physics-videos | grep -269 #+end_src271 #+results:272 : lower-left.flv273 : lower-right.flv274 : upper-left.flv275 : upper-right.flv277 The videos are fused together with the following =gstreamer= commands:279 #+begin_src sh :results silent280 cd physics-videos282 gst-launch-0.10 \283 filesrc location=./upper-right.flv ! decodebin ! \284 videoscale ! ffmpegcolorspace ! \285 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \286 videobox border-alpha=0 left=-640 ! \287 videomixer name=mix ! ffmpegcolorspace ! videorate ! \288 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \289 jpegenc ! avimux ! filesink location=upper.flv \290 \291 filesrc location=./upper-left.flv ! decodebin ! \292 videoscale ! ffmpegcolorspace ! \293 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \294 videobox right=-640 ! mix.295 #+end_src297 #+begin_src sh :results silent298 cd physics-videos300 gst-launch-0.10 \301 filesrc location=./lower-left.flv ! decodebin ! \302 videoscale ! ffmpegcolorspace ! \303 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \304 videobox border-alpha=0 left=-640 ! \305 videomixer name=mix ! ffmpegcolorspace ! videorate ! \306 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \307 jpegenc ! avimux ! filesink location=lower.flv \308 \309 filesrc location=./lower-right.flv ! decodebin ! \310 videoscale ! ffmpegcolorspace ! \311 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \312 videobox right=-640 ! mix.313 #+end_src315 #+begin_src sh :results silent316 cd physics-videos318 gst-launch-0.10 \319 filesrc location=./upper.flv ! decodebin ! \320 videoscale ! ffmpegcolorspace ! \321 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \322 videobox border-alpha=0 bottom=-480 ! \323 videomixer name=mix ! ffmpegcolorspace ! videorate ! \324 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \325 jpegenc ! avimux ! filesink location=../youtube/helloPhysics.flv \326 \327 filesrc location=./lower.flv ! decodebin ! \328 videoscale ! ffmpegcolorspace ! \329 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \330 videobox top=-480 ! mix.331 #+end_src333 #+begin_src sh :results verbatim334 du -h youtube/helloPhysics.flv335 #+end_src337 #+results:338 : 180M physics-videos/helloPhysics.flv341 Thats a terribly large size!342 Let's compress it:344 ** Compressing the HelloPhysics Video345 First, we'll scale the video, then, we'll decrease it's bitrate. The346 end result will be perfect for upload to YouTube.348 #+begin_src sh :results silent349 cd youtube351 gst-launch-0.10 \352 filesrc location=./helloPhysics.flv ! decodebin ! \353 videoscale ! ffmpegcolorspace ! \354 `: # the original size is 1280 by 960` \355 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \356 videoscale ! \357 `: # here we scale the video down` \358 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \359 `: # and here we limit the bitrate` \360 theoraenc bitrate=1024 quality=30 ! \361 oggmux ! progressreport update-freq=1 ! \362 filesink location=./helloPhysics.ogg363 #+end_src365 #+begin_src sh :results verbatim366 du -h youtube/helloPhysics.ogg367 #+end_src369 #+results:370 : 13M youtube/helloPhysics.ogg372 [[http://www.youtube.com/watch?v=WIJt9aRGusc][helloPhysics.ogg]]374 #+begin_html375 <iframe width="425" height="349"376 src="http://www.youtube.com/embed/WIJt9aRGusc?hl=en&fs=1"377 frameborder="0" allowfullscreen>378 </iframe>379 #+end_html382 ** COMMENT failed attempts383 Let's try the [[http://diracvideo.org/][Dirac]] video encoder.385 #+begin_src sh :results verbatim386 cd youtube387 START=$(date +%s)388 gst-launch-0.10 \389 filesrc location=./helloPhysics.flv ! decodebin ! \390 videoscale ! ffmpegcolorspace ! \391 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \392 schroenc ! filesink location=./helloPhysics.drc > /dev/null393 echo `expr $(( $(date +%s) - $START))`394 #+end_src397 #+results:398 : 142400 That took 142 seconds. Let's see how it does compression-wise:402 #+begin_src sh :results verbatim403 du -h ./youtube/helloPhysics.drc404 #+end_src406 #+results:407 : 22M ./physics-videos/helloPhysics.drc410 #+begin_src sh :results verbatim411 cd youtube412 START=$(date +%s)413 gst-launch-0.10 \414 filesrc location=./helloPhysics.flv ! decodebin ! \415 videoscale ! ffmpegcolorspace ! \416 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \417 theoraenc ! oggmux ! filesink location=./helloPhysics.ogg \418 > /dev/null419 echo `expr $(( $(date +%s) - $START))`420 #+end_src422 #+results:423 : 123425 #+begin_src sh :results verbatim426 du -h youtube/helloPhysics.ogg427 #+end_src429 #+results:430 : 59M physics-videos/helloPhysics.ogg433 =*.drc= files can not be uploaded to YouTube, so I'll go for the434 avi file.437 ** COMMENT text for videos438 Video output from JMonkeyEngine3 (www.jmonkeyengine.org/) using Xuggle439 (www.xuggle.com/). Everything is explained at440 http://aurellem.org/cortex/capture-video.html.443 Video output from JMonkeyEngine3 (www.jmonkeyengine.org/) HelloPhysics444 demo application using Xuggle (www.xuggle.com/). Everything is445 explained at http://aurellem.org/cortex/capture-video.html. Here,446 four points of view are simultaneously recorded and then glued447 together later.449 JME3 Xuggle Aurellem video capture452 * Sample Videos453 I encoded most of the original JME3 Hello demos for your viewing454 pleasure, all using the =VideoProcessor= and =IsoTimer= classes.456 ** HelloTerrain457 [[http://youtu.be/5_4wyDFwrVQ][HelloTerrain.avi]]459 #+begin_html460 <iframe width="425" height="349"461 src="http://www.youtube.com/embed/5_4wyDFwrVQ"462 frameborder="0" allowfullscreen>463 </iframe>464 #+end_html466 ** HelloAssets467 [[http://www.youtube.com/watch?v=oGg-Q6k1BM4][HelloAssets.avi]]469 #+begin_html470 <iframe width="425" height="349"471 src="http://www.youtube.com/embed/oGg-Q6k1BM4?hl=en&fs=1"472 frameborder="0" allowfullscreen>473 </iframe>474 #+end_html476 ** HelloEffects477 [[http://www.youtube.com/watch?v=TuxlLMe53hA][HelloEffects]]479 #+begin_html480 <iframe width="425" height="349"481 src="http://www.youtube.com/embed/TuxlLMe53hA?hl=en&fs=1"482 frameborder="0" allowfullscreen>483 </iframe>484 #+end_html486 ** HelloCollision487 [[http://www.youtube.com/watch?v=GPlvJkiZfFw][HelloCollision.avi]]489 #+begin_html490 <iframe width="425" height="349"491 src="http://www.youtube.com/embed/GPlvJkiZfFw?hl=en&fs=1"492 frameborder="0" allowfullscreen>493 </iframe>494 #+end_html496 ** HelloAnimation497 [[http://www.youtube.com/watch?v=SDCfOSPYUkg][HelloAnimation.avi]]499 #+begin_html500 <iframe width="425" height="349"501 src="http://www.youtube.com/embed/SDCfOSPYUkg?hl=en&fs=1"502 frameborder="0" allowfullscreen>503 </iframe>504 #+end_html506 ** HelloNode507 [[http://www.youtube.com/watch?v=pL-0fR0-ilQ][HelloNode.avi]]509 #+begin_html510 <iframe width="425" height="349"511 src="http://www.youtube.com/embed/pL-0fR0-ilQ?hl=en&fs=1"512 frameborder="0" allowfullscreen>513 </iframe>514 #+end_html516 ** HelloLoop517 [[http://www.youtube.com/watch?v=mosZzzcdE5w][HelloLoop.avi]]519 #+begin_html520 <iframe width="425" height="349"521 src="http://www.youtube.com/embed/mosZzzcdE5w?hl=en&fs=1"522 frameborder="0" allowfullscreen>523 </iframe>524 #+end_html527 *** COMMENT x-form the other stupid528 progressreport update-freq=1530 gst-launch-0.10 \531 filesrc location=./helloPhy ! decodebin ! \532 videoscale ! ffmpegcolorspace ! \533 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \534 x264enc ! avimux ! filesink location=helloPhysics.avi \537 gst-launch-0.10 \538 filesrc location=./HelloAnimationStatic.flv ! decodebin ! \539 videoscale ! ffmpegcolorspace ! \540 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \541 videobox border-alpha=0 left=-640 ! \542 videomixer name=mix ! ffmpegcolorspace ! videorate ! \543 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \544 x264enc ! avimux ! progressreport update-freq=1 ! \545 filesink location=../youtube/HelloAnimation.avi \546 \547 filesrc location=./HelloAnimationMotion.flv ! decodebin ! \548 videoscale ! ffmpegcolorspace ! \549 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \550 videobox right=-640 ! mix.552 gst-launch-0.10 \553 filesrc location=./HelloCollisionMotion.flv ! decodebin ! \554 videoscale ! ffmpegcolorspace ! \555 video/x-raw-yuv, width=800, height=600, framerate=25/1 ! \556 x264enc bitrate=1024 ! avimux ! \557 filesink location=../youtube/HelloCollision.avi559 gst-launch-0.10 \560 filesrc location=./HelloEffectsStatic.flv ! decodebin ! \561 videoscale ! ffmpegcolorspace ! \562 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \563 videobox border-alpha=0 left=-640 ! \564 videomixer name=mix ! ffmpegcolorspace ! videorate ! \565 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \566 x264enc bitrate=1024 ! avimux ! progressreport update-freq=1 ! \567 filesink location=../youtube/HelloEffects.avi \568 \569 filesrc location=./HelloEffectsMotion.flv ! decodebin ! \570 videoscale ! ffmpegcolorspace ! \571 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \572 videobox right=-640 ! mix.574 gst-launch-0.10 \575 filesrc location=./HelloTerrainMotion.flv ! decodebin ! \576 videoscale ! ffmpegcolorspace ! \577 video/x-raw-yuv, width=800, height=600, framerate=25/1 ! \578 x264enc bitrate=1024 ! avimux ! \579 filesink location=../youtube/HelloTerrain.avi582 gst-launch-0.10 \583 filesrc location=./HelloAssetsStatic.flv ! decodebin ! \584 videoscale ! ffmpegcolorspace ! \585 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \586 videobox border-alpha=0 left=-640 ! \587 videomixer name=mix ! ffmpegcolorspace ! videorate ! \588 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \589 x264enc bitrate=1024 ! avimux ! progressreport update-freq=1 ! \590 filesink location=../youtube/HelloAssets.avi \591 \592 filesrc location=./HelloAssetsMotion.flv ! decodebin ! \593 videoscale ! ffmpegcolorspace ! \594 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \595 videobox right=-640 ! mix.598 gst-launch-0.10 \599 filesrc location=./HelloNodeStatic.flv ! decodebin ! \600 videoscale ! ffmpegcolorspace ! \601 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \602 videobox border-alpha=0 left=-640 ! \603 videomixer name=mix ! ffmpegcolorspace ! videorate ! \604 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \605 x264enc bitrate=1024 ! avimux ! progressreport update-freq=1 ! \606 filesink location=../youtube/HelloNode.avi \607 \608 filesrc location=./HelloNodeMotion.flv ! decodebin ! \609 videoscale ! ffmpegcolorspace ! \610 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \611 videobox right=-640 ! mix.613 gst-launch-0.10 \614 filesrc location=./HelloLoopStatic.flv ! decodebin ! \615 videoscale ! ffmpegcolorspace ! \616 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \617 videobox border-alpha=0 left=-640 ! \618 videomixer name=mix ! ffmpegcolorspace ! videorate ! \619 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \620 x264enc bitrate=1024 ! avimux ! progressreport update-freq=1 ! \621 filesink location=../youtube/HelloLoop.avi \622 \623 filesrc location=./HelloLoopMotion.flv ! decodebin ! \624 videoscale ! ffmpegcolorspace ! \625 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \626 videobox right=-640 ! mix.