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@0
|
146 =./jme3/src/core/com/jme3/system/IsoTimer.java=
|
rlm@0
|
147 #+include ./jme3/src/core/com/jme3/system/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 In order to facilitate setting the =Timer= in user code, I added
|
rlm@0
|
154 getter and setter methods to =Application.java=.
|
rlm@0
|
155
|
rlm@0
|
156 In =./jme3/src/core/com/jme3/app/Application.java= I added:
|
rlm@0
|
157 #+include ./jme3/src/core/com/jme3/app/Application.java src java :lines "340-356"
|
rlm@0
|
158
|
rlm@0
|
159 * Encoding to Video
|
rlm@0
|
160
|
rlm@0
|
161 Now that the issue of time is solved, we just need a function that
|
rlm@0
|
162 writes each frame to a video. We can put this function somewhere
|
rlm@0
|
163 where it will be called exactly one per frame.
|
rlm@0
|
164
|
rlm@0
|
165 JME3 already provides exactly the class we need: the =SceneProcessor=
|
rlm@0
|
166 class can be attached to any viewport and the methods defined therein
|
rlm@0
|
167 will be called at the appropriate points in the rendering process.
|
rlm@0
|
168
|
rlm@0
|
169 If you want to generate video from Java, a great option is [[http://www.xuggle.com/][Xuggle]]. It
|
rlm@0
|
170 takes care of everything related to video encoding and decoding and
|
rlm@0
|
171 runs on Windows, Linux and Mac. Out of all the video frameworks for
|
rlm@0
|
172 Java I personally like this one the best.
|
rlm@0
|
173
|
rlm@0
|
174 Here is a =SceneProcessor= that uses [[http://www.xuggle.com/][Xuggle]] to write each frame to a
|
rlm@0
|
175 video file.
|
rlm@0
|
176
|
rlm@0
|
177 =./jme3/src/core/com/jme3/app/VideoProcessor.java=
|
rlm@0
|
178 #+include ./jme3/src/core/com/jme3/app/VideoProcessor.java src java
|
rlm@0
|
179
|
rlm@0
|
180 With this, we are able to record video!
|
rlm@0
|
181
|
rlm@0
|
182 * Hello Video!
|
rlm@0
|
183
|
rlm@0
|
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
|
rlm@0
|
185 augmented it with video output as follows:
|
rlm@0
|
186
|
rlm@0
|
187 =./jme3/src/test/jme3test/helloworld/HelloVideo.java=
|
rlm@0
|
188 #+include ./jme3/src/test/jme3test/helloworld/HelloVideo.java src java
|
rlm@0
|
189
|
rlm@0
|
190 The videos are created in the =hello-video= directory
|
rlm@0
|
191
|
rlm@0
|
192 #+begin_src sh :results verbatim
|
rlm@0
|
193 du -h hello-video/*
|
rlm@0
|
194 #+end_src
|
rlm@0
|
195
|
rlm@0
|
196 #+results:
|
rlm@0
|
197 : 932K hello-video/hello-video-moving.flv
|
rlm@0
|
198 : 640K hello-video/hello-video-static.flv
|
rlm@0
|
199
|
rlm@0
|
200 And can be immediately uploaded to youtube
|
rlm@0
|
201
|
rlm@0
|
202 - [[http://www.youtube.com/watch?v=C8gxVAySaPg][hello-video-moving.flv]]
|
rlm@0
|
203 #+BEGIN_HTML
|
rlm@0
|
204 <iframe width="425" height="349"
|
rlm@0
|
205 src="http://www.youtube.com/embed/C8gxVAySaPg"
|
rlm@0
|
206 frameborder="0" allowfullscreen>
|
rlm@0
|
207 </iframe>
|
rlm@0
|
208 #+END_HTML
|
rlm@0
|
209 - [[http://www.youtube.com/watch?v=pHcFOtIS07Q][hello-video-static.flv]]
|
rlm@0
|
210 #+BEGIN_HTML
|
rlm@0
|
211 <iframe width="425" height="349"
|
rlm@0
|
212 src="http://www.youtube.com/embed/pHcFOtIS07Q"
|
rlm@0
|
213 frameborder="0" allowfullscreen>
|
rlm@0
|
214 </iframe>
|
rlm@0
|
215
|
rlm@0
|
216 #+END_HTML
|
rlm@0
|
217
|
rlm@0
|
218
|
rlm@0
|
219
|
rlm@0
|
220 * Summary
|
rlm@0
|
221 It's quite easy to augment your own application to record video,
|
rlm@0
|
222 almost regardless of how complicated the actual application is. You
|
rlm@0
|
223 can also record from multiple ViewPorts as the above example shows.
|
rlm@0
|
224
|
rlm@0
|
225 The process for adding video recording to your application is as
|
rlm@0
|
226 follows:
|
rlm@0
|
227
|
rlm@0
|
228 Assuming you want to record at 30 fps, add:
|
rlm@0
|
229
|
rlm@0
|
230 #+begin_src java :exports code
|
rlm@0
|
231 this.setTimer(new IsoTimer(30));
|
rlm@0
|
232 #+end_src
|
rlm@0
|
233
|
rlm@0
|
234 Somewhere in the initialization of your Application. Right now, you
|
rlm@0
|
235 will have to add the =setTimer= method to =Application=, but hopefully
|
rlm@0
|
236 this method will be included soon by the JMonkeyEngine3 team.
|
rlm@0
|
237
|
rlm@0
|
238 Then, you create a =VideoProcessor= object and attach it to the
|
rlm@0
|
239 =ViewPort= from which you want to record.
|
rlm@0
|
240
|
rlm@0
|
241 If you want to record from the game's main =ViewPort= to a file called
|
rlm@0
|
242 =/home/r/record.flv=, then add:
|
rlm@0
|
243
|
rlm@0
|
244 #+begin_src java :exports code
|
rlm@0
|
245 viewPort.addProcessor(new VideoProcessor(new File("/home/r/record.flv")));
|
rlm@0
|
246 #+end_src
|
rlm@0
|
247
|
rlm@0
|
248 Do this for each =ViewPort= from which you want to record. The more
|
rlm@0
|
249 ViewPorts from which you record, the slower the game will run, but
|
rlm@0
|
250 this slowness will not affect the final video output.
|
rlm@0
|
251
|
rlm@0
|
252 * More Examples
|
rlm@0
|
253 ** Hello Physics
|
rlm@0
|
254 =HelloVideo= is boring. Let's add some video capturing to =HelloPhysics=
|
rlm@0
|
255 and create something fun!
|
rlm@0
|
256
|
rlm@0
|
257 This example is a modified version of =HelloPhysics= that creates four
|
rlm@0
|
258 simultaneous views of the same scene of cannonballs careening into a
|
rlm@0
|
259 brick wall.
|
rlm@0
|
260
|
rlm@0
|
261 =./jme3/src/test/jme3test/helloworld/HelloPhysicsWithVideo.java=
|
rlm@0
|
262 #+include ./jme3/src/test/jme3test/helloworld/HelloPhysicsWithVideo.java src java
|
rlm@0
|
263
|
rlm@0
|
264 Running the program outputs four videos into the =./physics-videos=
|
rlm@0
|
265 directory.
|
rlm@0
|
266
|
rlm@0
|
267 #+begin_src sh :exports both :results verbatim
|
rlm@0
|
268 ls ./physics-videos | grep -
|
rlm@0
|
269 #+end_src
|
rlm@0
|
270
|
rlm@0
|
271 #+results:
|
rlm@0
|
272 : lower-left.flv
|
rlm@0
|
273 : lower-right.flv
|
rlm@0
|
274 : upper-left.flv
|
rlm@0
|
275 : upper-right.flv
|
rlm@0
|
276
|
rlm@0
|
277 The videos are fused together with the following =gstreamer= commands:
|
rlm@0
|
278
|
rlm@0
|
279 #+begin_src sh :results silent
|
rlm@0
|
280 cd physics-videos
|
rlm@0
|
281
|
rlm@0
|
282 gst-launch-0.10 \
|
rlm@0
|
283 filesrc location=./upper-right.flv ! decodebin ! \
|
rlm@0
|
284 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
285 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
286 videobox border-alpha=0 left=-640 ! \
|
rlm@0
|
287 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
|
rlm@0
|
288 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
|
rlm@0
|
289 jpegenc ! avimux ! filesink location=upper.flv \
|
rlm@0
|
290 \
|
rlm@0
|
291 filesrc location=./upper-left.flv ! decodebin ! \
|
rlm@0
|
292 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
293 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
294 videobox right=-640 ! mix.
|
rlm@0
|
295 #+end_src
|
rlm@0
|
296
|
rlm@0
|
297 #+begin_src sh :results silent
|
rlm@0
|
298 cd physics-videos
|
rlm@0
|
299
|
rlm@0
|
300 gst-launch-0.10 \
|
rlm@0
|
301 filesrc location=./lower-left.flv ! decodebin ! \
|
rlm@0
|
302 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
303 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
304 videobox border-alpha=0 left=-640 ! \
|
rlm@0
|
305 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
|
rlm@0
|
306 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
|
rlm@0
|
307 jpegenc ! avimux ! filesink location=lower.flv \
|
rlm@0
|
308 \
|
rlm@0
|
309 filesrc location=./lower-right.flv ! decodebin ! \
|
rlm@0
|
310 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
311 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
312 videobox right=-640 ! mix.
|
rlm@0
|
313 #+end_src
|
rlm@0
|
314
|
rlm@0
|
315 #+begin_src sh :results silent
|
rlm@0
|
316 cd physics-videos
|
rlm@0
|
317
|
rlm@0
|
318 gst-launch-0.10 \
|
rlm@0
|
319 filesrc location=./upper.flv ! decodebin ! \
|
rlm@0
|
320 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
321 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
|
rlm@0
|
322 videobox border-alpha=0 bottom=-480 ! \
|
rlm@0
|
323 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
|
rlm@0
|
324 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \
|
rlm@0
|
325 jpegenc ! avimux ! filesink location=../youtube/helloPhysics.flv \
|
rlm@0
|
326 \
|
rlm@0
|
327 filesrc location=./lower.flv ! decodebin ! \
|
rlm@0
|
328 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
329 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
|
rlm@0
|
330 videobox top=-480 ! mix.
|
rlm@0
|
331 #+end_src
|
rlm@0
|
332
|
rlm@0
|
333 #+begin_src sh :results verbatim
|
rlm@0
|
334 du -h youtube/helloPhysics.flv
|
rlm@0
|
335 #+end_src
|
rlm@0
|
336
|
rlm@0
|
337 #+results:
|
rlm@0
|
338 : 180M physics-videos/helloPhysics.flv
|
rlm@0
|
339
|
rlm@0
|
340
|
rlm@0
|
341 Thats a terribly large size!
|
rlm@0
|
342 Let's compress it:
|
rlm@0
|
343
|
rlm@0
|
344 ** Compressing the HelloPhysics Video
|
rlm@0
|
345 First, we'll scale the video, then, we'll decrease it's bitrate. The
|
rlm@0
|
346 end result will be perfect for upload to YouTube.
|
rlm@0
|
347
|
rlm@0
|
348 #+begin_src sh :results silent
|
rlm@0
|
349 cd youtube
|
rlm@0
|
350
|
rlm@0
|
351 gst-launch-0.10 \
|
rlm@0
|
352 filesrc location=./helloPhysics.flv ! decodebin ! \
|
rlm@0
|
353 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
354 `: # the original size is 1280 by 960` \
|
rlm@0
|
355 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \
|
rlm@0
|
356 videoscale ! \
|
rlm@0
|
357 `: # here we scale the video down` \
|
rlm@0
|
358 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
359 `: # and here we limit the bitrate` \
|
rlm@0
|
360 theoraenc bitrate=1024 quality=30 ! \
|
rlm@0
|
361 oggmux ! progressreport update-freq=1 ! \
|
rlm@0
|
362 filesink location=./helloPhysics.ogg
|
rlm@0
|
363 #+end_src
|
rlm@0
|
364
|
rlm@0
|
365 #+begin_src sh :results verbatim
|
rlm@0
|
366 du -h youtube/helloPhysics.ogg
|
rlm@0
|
367 #+end_src
|
rlm@0
|
368
|
rlm@0
|
369 #+results:
|
rlm@0
|
370 : 13M youtube/helloPhysics.ogg
|
rlm@0
|
371
|
rlm@0
|
372 [[http://www.youtube.com/watch?v=WIJt9aRGusc][helloPhysics.ogg]]
|
rlm@0
|
373
|
rlm@0
|
374 #+begin_html
|
rlm@0
|
375 <iframe width="425" height="349"
|
rlm@0
|
376 src="http://www.youtube.com/embed/WIJt9aRGusc?hl=en&fs=1"
|
rlm@0
|
377 frameborder="0" allowfullscreen>
|
rlm@0
|
378 </iframe>
|
rlm@0
|
379 #+end_html
|
rlm@0
|
380
|
rlm@0
|
381
|
rlm@0
|
382 ** COMMENT failed attempts
|
rlm@0
|
383 Let's try the [[http://diracvideo.org/][Dirac]] video encoder.
|
rlm@0
|
384
|
rlm@0
|
385 #+begin_src sh :results verbatim
|
rlm@0
|
386 cd youtube
|
rlm@0
|
387 START=$(date +%s)
|
rlm@0
|
388 gst-launch-0.10 \
|
rlm@0
|
389 filesrc location=./helloPhysics.flv ! decodebin ! \
|
rlm@0
|
390 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
391 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \
|
rlm@0
|
392 schroenc ! filesink location=./helloPhysics.drc > /dev/null
|
rlm@0
|
393 echo `expr $(( $(date +%s) - $START))`
|
rlm@0
|
394 #+end_src
|
rlm@0
|
395
|
rlm@0
|
396
|
rlm@0
|
397 #+results:
|
rlm@0
|
398 : 142
|
rlm@0
|
399
|
rlm@0
|
400 That took 142 seconds. Let's see how it does compression-wise:
|
rlm@0
|
401
|
rlm@0
|
402 #+begin_src sh :results verbatim
|
rlm@0
|
403 du -h ./youtube/helloPhysics.drc
|
rlm@0
|
404 #+end_src
|
rlm@0
|
405
|
rlm@0
|
406 #+results:
|
rlm@0
|
407 : 22M ./physics-videos/helloPhysics.drc
|
rlm@0
|
408
|
rlm@0
|
409
|
rlm@0
|
410 #+begin_src sh :results verbatim
|
rlm@0
|
411 cd youtube
|
rlm@0
|
412 START=$(date +%s)
|
rlm@0
|
413 gst-launch-0.10 \
|
rlm@0
|
414 filesrc location=./helloPhysics.flv ! decodebin ! \
|
rlm@0
|
415 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
416 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \
|
rlm@0
|
417 theoraenc ! oggmux ! filesink location=./helloPhysics.ogg \
|
rlm@0
|
418 > /dev/null
|
rlm@0
|
419 echo `expr $(( $(date +%s) - $START))`
|
rlm@0
|
420 #+end_src
|
rlm@0
|
421
|
rlm@0
|
422 #+results:
|
rlm@0
|
423 : 123
|
rlm@0
|
424
|
rlm@0
|
425 #+begin_src sh :results verbatim
|
rlm@0
|
426 du -h youtube/helloPhysics.ogg
|
rlm@0
|
427 #+end_src
|
rlm@0
|
428
|
rlm@0
|
429 #+results:
|
rlm@0
|
430 : 59M physics-videos/helloPhysics.ogg
|
rlm@0
|
431
|
rlm@0
|
432
|
rlm@0
|
433 =*.drc= files can not be uploaded to YouTube, so I'll go for the
|
rlm@0
|
434 avi file.
|
rlm@0
|
435
|
rlm@0
|
436
|
rlm@0
|
437 ** COMMENT text for videos
|
rlm@0
|
438 Video output from JMonkeyEngine3 (www.jmonkeyengine.org/) using Xuggle
|
rlm@0
|
439 (www.xuggle.com/). Everything is explained at
|
rlm@0
|
440 http://aurellem.org/cortex/capture-video.html.
|
rlm@0
|
441
|
rlm@0
|
442
|
rlm@0
|
443 Video output from JMonkeyEngine3 (www.jmonkeyengine.org/) HelloPhysics
|
rlm@0
|
444 demo application using Xuggle (www.xuggle.com/). Everything is
|
rlm@0
|
445 explained at http://aurellem.org/cortex/capture-video.html. Here,
|
rlm@0
|
446 four points of view are simultaneously recorded and then glued
|
rlm@0
|
447 together later.
|
rlm@0
|
448
|
rlm@0
|
449 JME3 Xuggle Aurellem video capture
|
rlm@0
|
450
|
rlm@0
|
451
|
rlm@0
|
452 * Sample Videos
|
rlm@0
|
453 I encoded most of the original JME3 Hello demos for your viewing
|
rlm@0
|
454 pleasure, all using the =VideoProcessor= and =IsoTimer= classes.
|
rlm@0
|
455
|
rlm@0
|
456 ** HelloTerrain
|
rlm@0
|
457 [[http://youtu.be/5_4wyDFwrVQ][HelloTerrain.avi]]
|
rlm@0
|
458
|
rlm@0
|
459 #+begin_html
|
rlm@0
|
460 <iframe width="425" height="349"
|
rlm@0
|
461 src="http://www.youtube.com/embed/5_4wyDFwrVQ"
|
rlm@0
|
462 frameborder="0" allowfullscreen>
|
rlm@0
|
463 </iframe>
|
rlm@0
|
464 #+end_html
|
rlm@0
|
465
|
rlm@0
|
466 ** HelloAssets
|
rlm@0
|
467 [[http://www.youtube.com/watch?v=oGg-Q6k1BM4][HelloAssets.avi]]
|
rlm@0
|
468
|
rlm@0
|
469 #+begin_html
|
rlm@0
|
470 <iframe width="425" height="349"
|
rlm@0
|
471 src="http://www.youtube.com/embed/oGg-Q6k1BM4?hl=en&fs=1"
|
rlm@0
|
472 frameborder="0" allowfullscreen>
|
rlm@0
|
473 </iframe>
|
rlm@0
|
474 #+end_html
|
rlm@0
|
475
|
rlm@0
|
476 ** HelloEffects
|
rlm@0
|
477 [[http://www.youtube.com/watch?v=TuxlLMe53hA][HelloEffects]]
|
rlm@0
|
478
|
rlm@0
|
479 #+begin_html
|
rlm@0
|
480 <iframe width="425" height="349"
|
rlm@0
|
481 src="http://www.youtube.com/embed/TuxlLMe53hA?hl=en&fs=1"
|
rlm@0
|
482 frameborder="0" allowfullscreen>
|
rlm@0
|
483 </iframe>
|
rlm@0
|
484 #+end_html
|
rlm@0
|
485
|
rlm@0
|
486 ** HelloCollision
|
rlm@0
|
487 [[http://www.youtube.com/watch?v=GPlvJkiZfFw][HelloCollision.avi]]
|
rlm@0
|
488
|
rlm@0
|
489 #+begin_html
|
rlm@0
|
490 <iframe width="425" height="349"
|
rlm@0
|
491 src="http://www.youtube.com/embed/GPlvJkiZfFw?hl=en&fs=1"
|
rlm@0
|
492 frameborder="0" allowfullscreen>
|
rlm@0
|
493 </iframe>
|
rlm@0
|
494 #+end_html
|
rlm@0
|
495
|
rlm@0
|
496 ** HelloAnimation
|
rlm@0
|
497 [[http://www.youtube.com/watch?v=SDCfOSPYUkg][HelloAnimation.avi]]
|
rlm@0
|
498
|
rlm@0
|
499 #+begin_html
|
rlm@0
|
500 <iframe width="425" height="349"
|
rlm@0
|
501 src="http://www.youtube.com/embed/SDCfOSPYUkg?hl=en&fs=1"
|
rlm@0
|
502 frameborder="0" allowfullscreen>
|
rlm@0
|
503 </iframe>
|
rlm@0
|
504 #+end_html
|
rlm@0
|
505
|
rlm@0
|
506 ** HelloNode
|
rlm@0
|
507 [[http://www.youtube.com/watch?v=pL-0fR0-ilQ][HelloNode.avi]]
|
rlm@0
|
508
|
rlm@0
|
509 #+begin_html
|
rlm@0
|
510 <iframe width="425" height="349"
|
rlm@0
|
511 src="http://www.youtube.com/embed/pL-0fR0-ilQ?hl=en&fs=1"
|
rlm@0
|
512 frameborder="0" allowfullscreen>
|
rlm@0
|
513 </iframe>
|
rlm@0
|
514 #+end_html
|
rlm@0
|
515
|
rlm@0
|
516 ** HelloLoop
|
rlm@0
|
517 [[http://www.youtube.com/watch?v=mosZzzcdE5w][HelloLoop.avi]]
|
rlm@0
|
518
|
rlm@0
|
519 #+begin_html
|
rlm@0
|
520 <iframe width="425" height="349"
|
rlm@0
|
521 src="http://www.youtube.com/embed/mosZzzcdE5w?hl=en&fs=1"
|
rlm@0
|
522 frameborder="0" allowfullscreen>
|
rlm@0
|
523 </iframe>
|
rlm@0
|
524 #+end_html
|
rlm@0
|
525
|
rlm@0
|
526
|
rlm@0
|
527 *** COMMENT x-form the other stupid
|
rlm@0
|
528 progressreport update-freq=1
|
rlm@0
|
529
|
rlm@0
|
530 gst-launch-0.10 \
|
rlm@0
|
531 filesrc location=./helloPhy ! decodebin ! \
|
rlm@0
|
532 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
533 video/x-raw-yuv, width=1280, height=960, framerate=25/1 ! \
|
rlm@0
|
534 x264enc ! avimux ! filesink location=helloPhysics.avi \
|
rlm@0
|
535
|
rlm@0
|
536
|
rlm@0
|
537 gst-launch-0.10 \
|
rlm@0
|
538 filesrc location=./HelloAnimationStatic.flv ! decodebin ! \
|
rlm@0
|
539 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
540 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
541 videobox border-alpha=0 left=-640 ! \
|
rlm@0
|
542 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
|
rlm@0
|
543 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
|
rlm@0
|
544 x264enc ! avimux ! progressreport update-freq=1 ! \
|
rlm@0
|
545 filesink location=../youtube/HelloAnimation.avi \
|
rlm@0
|
546 \
|
rlm@0
|
547 filesrc location=./HelloAnimationMotion.flv ! decodebin ! \
|
rlm@0
|
548 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
549 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
550 videobox right=-640 ! mix.
|
rlm@0
|
551
|
rlm@0
|
552 gst-launch-0.10 \
|
rlm@0
|
553 filesrc location=./HelloCollisionMotion.flv ! decodebin ! \
|
rlm@0
|
554 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
555 video/x-raw-yuv, width=800, height=600, framerate=25/1 ! \
|
rlm@0
|
556 x264enc bitrate=1024 ! avimux ! \
|
rlm@0
|
557 filesink location=../youtube/HelloCollision.avi
|
rlm@0
|
558
|
rlm@0
|
559 gst-launch-0.10 \
|
rlm@0
|
560 filesrc location=./HelloEffectsStatic.flv ! decodebin ! \
|
rlm@0
|
561 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
562 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
563 videobox border-alpha=0 left=-640 ! \
|
rlm@0
|
564 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
|
rlm@0
|
565 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
|
rlm@0
|
566 x264enc bitrate=1024 ! avimux ! progressreport update-freq=1 ! \
|
rlm@0
|
567 filesink location=../youtube/HelloEffects.avi \
|
rlm@0
|
568 \
|
rlm@0
|
569 filesrc location=./HelloEffectsMotion.flv ! decodebin ! \
|
rlm@0
|
570 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
571 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
572 videobox right=-640 ! mix.
|
rlm@0
|
573
|
rlm@0
|
574 gst-launch-0.10 \
|
rlm@0
|
575 filesrc location=./HelloTerrainMotion.flv ! decodebin ! \
|
rlm@0
|
576 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
577 video/x-raw-yuv, width=800, height=600, framerate=25/1 ! \
|
rlm@0
|
578 x264enc bitrate=1024 ! avimux ! \
|
rlm@0
|
579 filesink location=../youtube/HelloTerrain.avi
|
rlm@0
|
580
|
rlm@0
|
581
|
rlm@0
|
582 gst-launch-0.10 \
|
rlm@0
|
583 filesrc location=./HelloAssetsStatic.flv ! decodebin ! \
|
rlm@0
|
584 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
585 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
586 videobox border-alpha=0 left=-640 ! \
|
rlm@0
|
587 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
|
rlm@0
|
588 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
|
rlm@0
|
589 x264enc bitrate=1024 ! avimux ! progressreport update-freq=1 ! \
|
rlm@0
|
590 filesink location=../youtube/HelloAssets.avi \
|
rlm@0
|
591 \
|
rlm@0
|
592 filesrc location=./HelloAssetsMotion.flv ! decodebin ! \
|
rlm@0
|
593 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
594 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
595 videobox right=-640 ! mix.
|
rlm@0
|
596
|
rlm@0
|
597
|
rlm@0
|
598 gst-launch-0.10 \
|
rlm@0
|
599 filesrc location=./HelloNodeStatic.flv ! decodebin ! \
|
rlm@0
|
600 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
601 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
602 videobox border-alpha=0 left=-640 ! \
|
rlm@0
|
603 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
|
rlm@0
|
604 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
|
rlm@0
|
605 x264enc bitrate=1024 ! avimux ! progressreport update-freq=1 ! \
|
rlm@0
|
606 filesink location=../youtube/HelloNode.avi \
|
rlm@0
|
607 \
|
rlm@0
|
608 filesrc location=./HelloNodeMotion.flv ! decodebin ! \
|
rlm@0
|
609 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
610 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
611 videobox right=-640 ! mix.
|
rlm@0
|
612
|
rlm@0
|
613 gst-launch-0.10 \
|
rlm@0
|
614 filesrc location=./HelloLoopStatic.flv ! decodebin ! \
|
rlm@0
|
615 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
616 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
617 videobox border-alpha=0 left=-640 ! \
|
rlm@0
|
618 videomixer name=mix ! ffmpegcolorspace ! videorate ! \
|
rlm@0
|
619 video/x-raw-yuv, width=1280, height=480, framerate=25/1 ! \
|
rlm@0
|
620 x264enc bitrate=1024 ! avimux ! progressreport update-freq=1 ! \
|
rlm@0
|
621 filesink location=../youtube/HelloLoop.avi \
|
rlm@0
|
622 \
|
rlm@0
|
623 filesrc location=./HelloLoopMotion.flv ! decodebin ! \
|
rlm@0
|
624 videoscale ! ffmpegcolorspace ! \
|
rlm@0
|
625 video/x-raw-yuv, width=640, height=480, framerate=25/1 ! \
|
rlm@0
|
626 videobox right=-640 ! mix.
|
rlm@0
|
627
|
rlm@32
|
628
|
rlm@32
|
629
|
rlm@32
|
630
|
rlm@32
|
631
|
rlm@32
|
632
|
rlm@32
|
633
|
rlm@32
|
634
|
rlm@32
|
635
|
rlm@32
|
636
|
rlm@32
|
637
|
rlm@32
|
638
|
rlm@32
|
639
|
rlm@32
|
640
|
rlm@32
|
641
|
rlm@32
|
642
|
rlm@32
|
643
|
rlm@32
|
644
|
rlm@32
|
645
|
rlm@32
|
646
|
rlm@32
|
647
|
rlm@32
|
648
|
rlm@32
|
649
|
rlm@32
|
650
|
rlm@32
|
651
|
rlm@32
|
652
|
rlm@32
|
653
|