rlm@3
|
1 package com.aurellem.capture;
|
rlm@3
|
2
|
rlm@3
|
3 import java.io.File;
|
rlm@3
|
4 import java.io.IOException;
|
rlm@12
|
5 import java.util.concurrent.Callable;
|
rlm@3
|
6
|
rlm@12
|
7 import com.aurellem.capture.audio.MultiListener;
|
rlm@12
|
8 import com.aurellem.capture.audio.WaveFileWriter;
|
rlm@9
|
9 import com.aurellem.capture.video.AVIVideoRecorder;
|
rlm@10
|
10 import com.aurellem.capture.video.AbstractVideoRecorder;
|
rlm@39
|
11 import com.aurellem.capture.video.FileVideoRecorder;
|
rlm@10
|
12 import com.aurellem.capture.video.XuggleVideoRecorder;
|
rlm@3
|
13 import com.jme3.app.Application;
|
rlm@12
|
14 import com.jme3.audio.AudioRenderer;
|
rlm@12
|
15 import com.jme3.renderer.ViewPort;
|
rlm@12
|
16 import com.jme3.scene.Spatial;
|
rlm@49
|
17 import com.jme3.system.AppSettings;
|
rlm@44
|
18 import com.jme3.system.JmeSystem;
|
rlm@3
|
19
|
rlm@53
|
20 /**
|
rlm@68
|
21 * Use the methods in this class for capturing consistent,
|
rlm@68
|
22 * high quality video and audio from a jMonkeyEngine3
|
rlm@68
|
23 * application. To capture audio or video do the following:
|
rlm@53
|
24 *
|
rlm@68
|
25 * 1.) Set your application's timer to an IsoTimer. Create
|
rlm@68
|
26 * the IsoTimer with the desired video
|
rlm@68
|
27 * frames-per-second.
|
rlm@56
|
28 *
|
rlm@68
|
29 * 2.) Call captureAudio and/or captureVideo on the
|
rlm@68
|
30 * Application as desired before starting the
|
rlm@68
|
31 * Application.
|
rlm@55
|
32 *
|
rlm@68
|
33 * See the Basic and Advanced demos in the examples section
|
rlm@68
|
34 * for more information. If you have any trouble, please PM
|
rlm@68
|
35 * me on the jMonkeyEngine forums. My username is bortreb.
|
rlm@55
|
36 *
|
rlm@53
|
37 * @author Robert McIntyre
|
rlm@53
|
38 */
|
rlm@53
|
39
|
rlm@3
|
40 public class Capture {
|
rlm@3
|
41
|
rlm@56
|
42 /**
|
rlm@68
|
43 * Use this function to capture video from your
|
rlm@68
|
44 * application. You specify the framerate at which the
|
rlm@68
|
45 * video will be recording by setting the Application's
|
rlm@68
|
46 * timer to an IsoTimer created with the desired
|
rlm@56
|
47 * frames-per-second.
|
rlm@56
|
48 *
|
rlm@68
|
49 * There are three ways to record and they are selected
|
rlm@68
|
50 * by the properties of the file that you specify.
|
rlm@56
|
51 *
|
rlm@68
|
52 * 1.) (Preferred) If you supply an empty directory as
|
rlm@68
|
53 * the file, then the video will be saved as a
|
rlm@68
|
54 * sequence of .png files, one file per frame. The
|
rlm@68
|
55 * files start at 0000000.png and increment from
|
rlm@68
|
56 * there. You can then combine the frames into your
|
rlm@68
|
57 * preferred container/codec. If the directory is
|
rlm@68
|
58 * not empty, then writing video frames to it will
|
rlm@68
|
59 * fail, and nothing will be written.
|
rlm@56
|
60 *
|
rlm@68
|
61 * 2.) If the filename ends in ".avi" then the frames
|
rlm@68
|
62 * will be encoded as a RAW stream inside an AVI 1.0
|
rlm@68
|
63 * container. The resulting file will be quite
|
rlm@68
|
64 * large and you will probably want to re-encode it
|
rlm@68
|
65 * to your preferred container/codec format. Be
|
rlm@68
|
66 * advised that some video payers cannot process AVI
|
rlm@68
|
67 * with a RAW stream, and that AVI 1.0 files
|
rlm@68
|
68 * generated by this method that exceed 2.0GB are
|
rlm@68
|
69 * invalid according to the AVI 1.0 spec (but many
|
rlm@68
|
70 * programs can still deal with them.) Thanks to
|
rlm@68
|
71 * Werner Randelshofer for his excellent work which
|
rlm@68
|
72 * made AVI file writer option possible.
|
rlm@56
|
73 *
|
rlm@68
|
74 * 3.) Any non-directory file ending in anything other
|
rlm@68
|
75 * than ".avi" will be processed through Xuggle.
|
rlm@68
|
76 * Xuggle provides the option to use many
|
rlm@68
|
77 * codecs/containers, but you will have to install
|
rlm@68
|
78 * it on your system yourself in order to use this
|
rlm@68
|
79 * option. Please visit http://www.xuggle.com/ to
|
rlm@68
|
80 * learn how to do this.
|
rlm@56
|
81 *
|
rlm@56
|
82 * @param app The Application from which you wish to record Video.
|
rlm@56
|
83 * @param file The file to which the video will be captured.
|
rlm@56
|
84 * @throws IOException
|
rlm@56
|
85 */
|
rlm@54
|
86
|
rlm@68
|
87 public static void captureVideo
|
rlm@68
|
88 (final Application app, final File file) throws IOException{
|
rlm@56
|
89 final AbstractVideoRecorder videoRecorder;
|
rlm@12
|
90
|
rlm@56
|
91 if (file.getCanonicalPath().endsWith(".avi")){
|
rlm@56
|
92 videoRecorder = new AVIVideoRecorder(file);}
|
rlm@56
|
93 else if (file.isDirectory()){
|
rlm@56
|
94 videoRecorder = new FileVideoRecorder(file);}
|
rlm@68
|
95 else {
|
rlm@68
|
96 videoRecorder = new XuggleVideoRecorder(file);
|
rlm@68
|
97 }
|
rlm@12
|
98
|
rlm@56
|
99 Callable<Object> thunk = new Callable<Object>(){
|
rlm@56
|
100 public Object call(){
|
rlm@12
|
101
|
rlm@56
|
102 ViewPort viewPort =
|
rlm@56
|
103 app.getRenderManager()
|
rlm@68
|
104 .createPostView("aurellem video record",
|
rlm@68
|
105 app.getCamera());
|
rlm@12
|
106
|
rlm@56
|
107 viewPort.setClearFlags(false, false, false);
|
rlm@12
|
108
|
rlm@56
|
109 // get GUI node stuff
|
rlm@56
|
110 for (Spatial s : app.getGuiViewPort().getScenes()){
|
rlm@56
|
111 viewPort.attachScene(s);
|
rlm@56
|
112 }
|
rlm@12
|
113
|
rlm@56
|
114 app.getStateManager().attach(videoRecorder);
|
rlm@56
|
115 viewPort.addProcessor(videoRecorder);
|
rlm@56
|
116 return null;
|
rlm@56
|
117 }
|
rlm@56
|
118 };
|
rlm@56
|
119 app.enqueue(thunk);
|
rlm@56
|
120 }
|
rlm@12
|
121
|
rlm@54
|
122
|
rlm@56
|
123 /**
|
rlm@68
|
124 * Use this function to capture audio from your
|
rlm@68
|
125 * application. Audio data will be saved in linear PCM
|
rlm@68
|
126 * format at 44,100 hertz in the wav container format to
|
rlm@68
|
127 * the file that you specify.
|
rlm@56
|
128 *
|
rlm@68
|
129 * Note that you *have* to use an IsoTimer for your
|
rlm@68
|
130 * Application's timer while recording audio or the
|
rlm@68
|
131 * recording will fail. Also ensure that your IsoTimer
|
rlm@68
|
132 * obeys the following constraints:
|
rlm@56
|
133 *
|
rlm@68
|
134 * 1.) The frames-per-second value of the IsoTimer
|
rlm@68
|
135 * cannot be lower than 10 frames-per-second.
|
rlm@56
|
136 *
|
rlm@68
|
137 * 2.) The frames-per-second value of the IsoTimer must
|
rlm@68
|
138 * evenly divide 44,100.
|
rlm@56
|
139 *
|
rlm@68
|
140 * @param app The Application from which you wish to
|
rlm@68
|
141 * record Audio.
|
rlm@68
|
142 * @param file the target file to which you want to
|
rlm@68
|
143 * record audio data.
|
rlm@56
|
144 * @throws IOException
|
rlm@56
|
145 */
|
rlm@12
|
146
|
rlm@68
|
147 public static void captureAudio
|
rlm@68
|
148 (final Application app, final File file) throws IOException{
|
rlm@56
|
149 AppSettings settings = null;
|
rlm@68
|
150 if (app.getContext() != null){
|
rlm@68
|
151 settings = app.getContext().getSettings();}
|
rlm@56
|
152 if (settings == null){settings = new AppSettings(true);}
|
rlm@56
|
153 settings.setAudioRenderer("Send");
|
rlm@56
|
154 app.setSettings(settings);
|
rlm@49
|
155
|
rlm@56
|
156 JmeSystem.setSystemDelegate(new AurellemSystemDelegate());
|
rlm@49
|
157
|
rlm@56
|
158 final WaveFileWriter writer = new WaveFileWriter(file);
|
rlm@12
|
159
|
rlm@56
|
160 Callable<Object> thunk = new Callable<Object>(){
|
rlm@56
|
161 public Object call(){
|
rlm@56
|
162 AudioRenderer ar = app.getAudioRenderer();
|
rlm@56
|
163 if (ar instanceof MultiListener){
|
rlm@56
|
164 MultiListener ml = (MultiListener)ar;
|
rlm@56
|
165 ml.registerSoundProcessor(writer);
|
rlm@56
|
166 }
|
rlm@56
|
167 return null;
|
rlm@56
|
168 }
|
rlm@56
|
169 };
|
rlm@56
|
170 app.enqueue(thunk);
|
rlm@56
|
171 }
|
rlm@3
|
172 }
|