Mercurial > jmeCapture
changeset 39:784a3f4e6202
updating capture-video
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Thu, 03 Nov 2011 16:00:46 -0700 |
parents | adeb88787645 |
children | 56dc950feaed |
files | .hgignore src/ca/randelshofer/AVIOutputStream.java src/com/aurellem/capture/Capture.java src/com/aurellem/capture/audio/AudioSendRenderer.java src/com/aurellem/capture/audio/SoundProcessor.java src/com/aurellem/capture/examples/AdvancedAudio.java src/com/aurellem/capture/examples/HelloVideo.java src/com/aurellem/capture/video/AVIVideoRecorder.java src/com/aurellem/capture/video/AbstractVideoRecorder.java src/com/aurellem/capture/video/FileVideoRecorder.java src/com/aurellem/capture/video/IVideoRecorder.java src/com/aurellem/capture/video/VideoRecorder.java |
diffstat | 12 files changed, 200 insertions(+), 140 deletions(-) [+] |
line wrap: on
line diff
1.1 --- a/.hgignore Mon Oct 31 07:43:44 2011 -0700 1.2 +++ b/.hgignore Thu Nov 03 16:00:46 2011 -0700 1.3 @@ -8,5 +8,5 @@ 1.4 dist* 1.5 lib* 1.6 .ant* 1.7 +output/* 1.8 1.9 -
2.1 --- a/src/ca/randelshofer/AVIOutputStream.java Mon Oct 31 07:43:44 2011 -0700 2.2 +++ b/src/ca/randelshofer/AVIOutputStream.java Thu Nov 03 16:00:46 2011 -0700 2.3 @@ -627,7 +627,7 @@ 2.4 } 2.5 2.6 /** 2.7 - * Sets the state of the QuickTimeOutpuStream to started. 2.8 + * Sets the state of the AVIOutputStream to 'started'. 2.9 * <p> 2.10 * If the state is changed by this method, the prolog is 2.11 * written.
3.1 --- a/src/com/aurellem/capture/Capture.java Mon Oct 31 07:43:44 2011 -0700 3.2 +++ b/src/com/aurellem/capture/Capture.java Thu Nov 03 16:00:46 2011 -0700 3.3 @@ -8,6 +8,7 @@ 3.4 import com.aurellem.capture.audio.WaveFileWriter; 3.5 import com.aurellem.capture.video.AVIVideoRecorder; 3.6 import com.aurellem.capture.video.AbstractVideoRecorder; 3.7 +import com.aurellem.capture.video.FileVideoRecorder; 3.8 import com.aurellem.capture.video.XuggleVideoRecorder; 3.9 import com.jme3.app.Application; 3.10 import com.jme3.audio.AudioRenderer; 3.11 @@ -27,6 +28,8 @@ 3.12 3.13 if (file.getCanonicalPath().endsWith(".avi")){ 3.14 videoRecorder = new AVIVideoRecorder(file);} 3.15 + else if (file.isDirectory()){ 3.16 + videoRecorder = new FileVideoRecorder(file);} 3.17 else { videoRecorder = new XuggleVideoRecorder(file);} 3.18 3.19 Callable<Object> thunk = new Callable<Object>(){
4.1 --- a/src/com/aurellem/capture/audio/AudioSendRenderer.java Mon Oct 31 07:43:44 2011 -0700 4.2 +++ b/src/com/aurellem/capture/audio/AudioSendRenderer.java Thu Nov 03 16:00:46 2011 -0700 4.3 @@ -136,8 +136,7 @@ 4.4 this.audioSend = new AudioSend(this.deviceID); 4.5 this.outFormat = audioSend.getAudioFormat(); 4.6 initBuffer(); 4.7 - System.out.println(outFormat); 4.8 - 4.9 + 4.10 // The LWJGL context must be established as the master context before 4.11 // any other listeners can be created on this device. 4.12 audioSend.initDevice(); 4.13 @@ -173,22 +172,14 @@ 4.14 } 4.15 4.16 4.17 - //public final static int BYTES_PER_SAMPLE = 4; 4.18 - 4.19 - 4.20 private ByteBuffer buffer;; 4.21 - private byte[] debug0; 4.22 - private byte[] debug1; 4.23 - 4.24 + 4.25 public static final int MIN_FRAMERATE = 10; 4.26 4.27 private void initBuffer(){ 4.28 int bufferSize = (int)(this.outFormat.getSampleRate() / ((float)MIN_FRAMERATE)) * 4.29 this.outFormat.getFrameSize(); 4.30 this.buffer = BufferUtils.createByteBuffer(bufferSize); 4.31 - debug0 = new byte[4096]; 4.32 - debug1 = new byte[4096]; 4.33 - 4.34 } 4.35 /* 4.36 4.37 @@ -196,33 +187,19 @@ 4.38 public void dispatchAudio(float tpf){ 4.39 4.40 int samplesToGet = (int) (tpf * outFormat.getSampleRate()); 4.41 - System.out.println("want " + samplesToGet + " samples"); 4.42 try {latch.await();} 4.43 catch (InterruptedException e) {e.printStackTrace();} 4.44 audioSend.step(samplesToGet); 4.45 updateAllListeners(); 4.46 - 4.47 + 4.48 for (int i = 0; i < this.listeners.size(); i++){ 4.49 buffer.clear(); 4.50 audioSend.getSamples(buffer, samplesToGet, i); 4.51 - if (i == 0 ) buffer.get(debug0); 4.52 - if (i == 1 ) buffer.get(debug1); 4.53 SoundProcessor sp = 4.54 - this.soundProcessorMap.get(this.listeners.get(i)); 4.55 + this.soundProcessorMap.get(this.listeners.get(i)); 4.56 if (null != sp){sp.process(buffer, samplesToGet*outFormat.getFrameSize(), outFormat);} 4.57 } 4.58 - 4.59 - for (int i = 0; i < samplesToGet; i++){ 4.60 - if (debug1[i] != debug0[i]){ 4.61 - System.out.println("inconsistency detected @ sample " + i); 4.62 - System.out.println("main : " + debug0[i]); 4.63 - System.out.println("aux : " + debug1[i]); 4.64 - 4.65 - break; 4.66 - } 4.67 - 4.68 - } 4.69 - 4.70 + 4.71 } 4.72 4.73 public void update(float tpf){
5.1 --- a/src/com/aurellem/capture/audio/SoundProcessor.java Mon Oct 31 07:43:44 2011 -0700 5.2 +++ b/src/com/aurellem/capture/audio/SoundProcessor.java Thu Nov 03 16:00:46 2011 -0700 5.3 @@ -6,8 +6,25 @@ 5.4 5.5 public interface SoundProcessor { 5.6 5.7 - void cleanup(); 5.8 + /** 5.9 + * Called when the SoundProcessor is being destroyed, and 5.10 + * there are no more samples to process. This happens at the 5.11 + * latest when the Application is shutting down. 5.12 + * 5.13 + */ 5.14 + void cleanup(); 5.15 5.16 - void process(ByteBuffer audioSamples, int numSamples, AudioFormat format); 5.17 + /** 5.18 + * 5.19 + * Called whenever there are new audio samples to process. The 5.20 + * audioSamples ByteBuffer contains 3D audio data rendered by 5.21 + * OpenAL. 5.22 + * 5.23 + * @param audioSamples a ByteBuffer containing processed audio 5.24 + * samples 5.25 + * @param numSamples the number of samples, in bytes, that are valid 5.26 + * @param format the format of the audio samples in audioSamples 5.27 + */ 5.28 + void process(ByteBuffer audioSamples, int numSamples, AudioFormat format); 5.29 5.30 }
6.1 --- a/src/com/aurellem/capture/examples/AdvancedAudio.java Mon Oct 31 07:43:44 2011 -0700 6.2 +++ b/src/com/aurellem/capture/examples/AdvancedAudio.java Thu Nov 03 16:00:46 2011 -0700 6.3 @@ -19,7 +19,6 @@ 6.4 import com.aurellem.capture.audio.WaveFileWriter; 6.5 import com.jme3.app.SimpleApplication; 6.6 import com.jme3.audio.AudioNode; 6.7 -import com.jme3.audio.AudioParam; 6.8 import com.jme3.audio.Listener; 6.9 import com.jme3.audio.ListenerParam; 6.10 import com.jme3.cinematic.MotionPath; 6.11 @@ -34,7 +33,6 @@ 6.12 import com.jme3.math.Vector3f; 6.13 import com.jme3.scene.Geometry; 6.14 import com.jme3.scene.Node; 6.15 -import com.jme3.scene.Spatial; 6.16 import com.jme3.scene.shape.Box; 6.17 import com.jme3.scene.shape.Sphere; 6.18 import com.jme3.system.AppSettings; 6.19 @@ -69,16 +67,18 @@ 6.20 app.setSettings(settings); 6.21 app.setShowSettings(false); 6.22 app.setPauseOnLostFocus(false); 6.23 - org.lwjgl.input.Mouse.setGrabbed(false); 6.24 - //try {Capture.captureVideo(app, new File("/home/r/tmp/out.avi"));} 6.25 - //catch (IOException e) {e.printStackTrace();} 6.26 + 6.27 + try {Capture.captureVideo(app, new File("/home/r/tmp/out")); 6.28 + Capture.captureAudio(app, new File("/home/r/tmp/main.wav"));} 6.29 + catch (IOException e) {e.printStackTrace();} 6.30 app.start(); 6.31 + 6.32 } 6.33 6.34 private MotionTrack motionControl; 6.35 6.36 6.37 - private Spatial makeEar(Node root, Vector3f position){ 6.38 + private Geometry makeEar(Node root, Vector3f position){ 6.39 Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); 6.40 Geometry ear = new Geometry("ear", new Box(1.0f, 1.0f, 1.0f)); 6.41 ear.setLocalTranslation(position); 6.42 @@ -90,10 +90,10 @@ 6.43 6.44 private Geometry bell; 6.45 6.46 - private Spatial ear1; 6.47 - //private Spatial ear2; 6.48 - //private Spatial ear3; 6.49 - //private Spatial ear4; 6.50 + private Geometry ear1; 6.51 + private Geometry ear2; 6.52 + private Geometry ear3; 6.53 + 6.54 6.55 6.56 private Vector3f[] path = new Vector3f[]{ 6.57 @@ -160,9 +160,9 @@ 6.58 rootNode.addLight(light); 6.59 6.60 ear1 = makeEar(rootNode, new Vector3f(0, 0 ,-20)); 6.61 - //ear2 = makeEar(rootNode, new Vector3f(0, 0 ,-20)); 6.62 - //ear3 = makeEar(rootNode, new Vector3f(20, 0 ,0)); 6.63 - //ear4 = makeEar(rootNode, new Vector3f(-20, 0 ,0)); 6.64 + ear2 = makeEar(rootNode, new Vector3f(0, 0 ,20)); 6.65 + ear3 = makeEar(rootNode, new Vector3f(20, 0 ,0)); 6.66 + 6.67 6.68 MotionPath track = new MotionPath(); 6.69 6.70 @@ -204,7 +204,7 @@ 6.71 6.72 6.73 private void initAudio() { 6.74 - 6.75 + org.lwjgl.input.Mouse.setGrabbed(false); 6.76 music = new AudioNode(assetManager, "Sound/Environment/sqr-1kHz.wav", false); 6.77 6.78 rootNode.attachChild(music); 6.79 @@ -234,14 +234,10 @@ 6.80 6.81 6.82 public class Dancer implements SoundProcessor { 6.83 - 6.84 - Spatial entity; 6.85 - 6.86 + Geometry entity; 6.87 float scale = 2; 6.88 - String debug; 6.89 - public Dancer(Spatial entity, String debug){ 6.90 + public Dancer(Geometry entity){ 6.91 this.entity = entity; 6.92 - this.debug = debug; 6.93 } 6.94 6.95 /** 6.96 @@ -265,10 +261,9 @@ 6.97 float max = Float.NEGATIVE_INFINITY; 6.98 for (float f : out){if (f > max) max = f;} 6.99 audioSamples.clear(); 6.100 - System.out.println(debug); 6.101 - System.out.println(max); 6.102 6.103 - 6.104 + if (max > 0.1){entity.getMaterial().setColor("Color", ColorRGBA.Green);} 6.105 + else {entity.getMaterial().setColor("Color", ColorRGBA.Gray);} 6.106 6.107 //entity.scale(this.scale); 6.108 //if (this.scale == 2f){this.scale = 0.5f;} 6.109 @@ -280,38 +275,35 @@ 6.110 6.111 6.112 6.113 + private void prepareEar(Geometry ear, int n){ 6.114 + if (this.audioRenderer instanceof MultiListener){ 6.115 + MultiListener rf = (MultiListener)this.audioRenderer; 6.116 + 6.117 + auxListener = new Listener(); 6.118 + auxListener.setLocation(ear.getLocalTranslation()); 6.119 + 6.120 + rf.addListener(auxListener); 6.121 + WaveFileWriter aux = null; 6.122 + 6.123 + try {aux = new WaveFileWriter(new File("/home/r/tmp/ear"+n+".wav"));} 6.124 + catch (FileNotFoundException e) {e.printStackTrace();} 6.125 + 6.126 + rf.registerSoundProcessor(auxListener, 6.127 + new CompositeSoundProcessor(new Dancer(ear), aux)); 6.128 + 6.129 + } 6.130 + } 6.131 + 6.132 6.133 public void simpleInitApp() { 6.134 this.setTimer(new IsoTimer(60)); 6.135 initAudio(); 6.136 initKeys(); 6.137 createScene(); 6.138 - listener.setLocation(ear1.getLocalTranslation()); 6.139 - listener.setRotation(new Quaternion().fromAngleAxis(0, Vector3f.UNIT_Y)); 6.140 - if (this.audioRenderer instanceof MultiListener){ 6.141 - MultiListener rf = (MultiListener)this.audioRenderer; 6.142 6.143 - 6.144 - 6.145 - auxListener = new Listener(listener); 6.146 - 6.147 - rf.addListener(auxListener); 6.148 - WaveFileWriter aux = null; 6.149 - WaveFileWriter main = null; 6.150 - 6.151 - 6.152 - try {aux = new WaveFileWriter(new File("/home/r/tmp/aux.wav"));} 6.153 - catch (FileNotFoundException e) {e.printStackTrace();} 6.154 - 6.155 - try {main = new WaveFileWriter(new File("/home/r/tmp/main.wav"));} 6.156 - catch (FileNotFoundException e) {e.printStackTrace();} 6.157 - 6.158 - rf.registerSoundProcessor(auxListener, 6.159 - new CompositeSoundProcessor(new Dancer(ear1, "aux"), aux)); 6.160 - 6.161 - rf.registerSoundProcessor( 6.162 - new CompositeSoundProcessor(new Dancer(ear1, "--------\nmain"), main)); 6.163 - } 6.164 + prepareEar(ear1, 1); 6.165 + prepareEar(ear2, 1); 6.166 + prepareEar(ear3, 1); 6.167 6.168 motionControl.play(); 6.169 } 6.170 @@ -350,15 +342,15 @@ 6.171 if (countdown == 0){ 6.172 music.play(); 6.173 } 6.174 - //Vector3f loc = cam.getLocation(); 6.175 - //Quaternion rot = cam.getRotation(); 6.176 - //listener.setLocation(loc); 6.177 - listener.setRotation(new Quaternion().fromAngleAxis(0, music.getLocalTranslation().subtract(listener.getLocation()))); 6.178 + Vector3f loc = cam.getLocation(); 6.179 + Quaternion rot = cam.getRotation(); 6.180 + listener.setLocation(loc); 6.181 + listener.setRotation(rot); 6.182 audioRenderer.updateListenerParam(listener, ListenerParam.Rotation); 6.183 6.184 - System.out.println(countdown); 6.185 + //System.out.println(countdown); 6.186 6.187 - if (countdown++ == 300) { this.requestClose(false);} 6.188 + //if (countdown++ == 300) { this.requestClose(false);} 6.189 6.190 //System.out.println("channel "+ music.getChannel()); 6.191 //listener.setLocation(cam.getLocation()); 6.192 @@ -378,8 +370,8 @@ 6.193 6.194 music.setLocalTranslation(bell.getLocalTranslation()); 6.195 6.196 - System.out.println("distance: " + 6.197 - music.getLocalTranslation().subtract(listener.getLocation()).length()); 6.198 + //System.out.println("distance: " + 6.199 + // music.getLocalTranslation().subtract(listener.getLocation()).length()); 6.200 6.201 //music.setVelocity(bellVelocity); 6.202
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 7.2 +++ b/src/com/aurellem/capture/examples/HelloVideo.java Thu Nov 03 16:00:46 2011 -0700 7.3 @@ -0,0 +1,22 @@ 7.4 +package com.aurellem.capture.examples; 7.5 + 7.6 +import java.io.File; 7.7 +import java.io.IOException; 7.8 + 7.9 +import jme3test.helloworld.HelloLoop; 7.10 + 7.11 +import com.aurellem.capture.IsoTimer; 7.12 +import com.aurellem.capture.Capture; 7.13 +import com.jme3.app.SimpleApplication; 7.14 + 7.15 +public class HelloVideo { 7.16 + 7.17 + public static void main(String[] ignore) throws IOException { 7.18 + SimpleApplication app = new HelloLoop(); 7.19 + File video = File.createTempFile("helloVideo", ".avi"); 7.20 + app.setTimer(new IsoTimer(60)); 7.21 + Capture.captureVideo(app, video); 7.22 + app.start(); 7.23 + System.out.println(video.getCanonicalPath()); 7.24 + } 7.25 +}
8.1 --- a/src/com/aurellem/capture/video/AVIVideoRecorder.java Mon Oct 31 07:43:44 2011 -0700 8.2 +++ b/src/com/aurellem/capture/video/AVIVideoRecorder.java Thu Nov 03 16:00:46 2011 -0700 8.3 @@ -9,38 +9,38 @@ 8.4 8.5 public class AVIVideoRecorder extends AbstractVideoRecorder{ 8.6 8.7 - AVIOutputStream out = null; 8.8 - boolean videoReady = false; 8.9 - BufferedImage frame; 8.10 + AVIOutputStream out = null; 8.11 + boolean videoReady = false; 8.12 + BufferedImage frame; 8.13 8.14 - public AVIVideoRecorder(File output) throws IOException { 8.15 - super(output); 8.16 - this.out = new AVIOutputStream(output, AVIOutputStream.VideoFormat.PNG, 24); 8.17 - this.out.setVideoCompressionQuality(1.0f); 8.18 - } 8.19 - 8.20 + public AVIVideoRecorder(File output) throws IOException { 8.21 + super(output); 8.22 + this.out = new 8.23 + AVIOutputStream(output, AVIOutputStream.VideoFormat.RAW, 24); 8.24 + this.out.setFrameRate(60); 8.25 + } 8.26 8.27 - public void initVideo (){ 8.28 - frame = new BufferedImage( 8.29 - width, height, 8.30 - BufferedImage.TYPE_INT_RGB); 8.31 - out.setFrameRate((int) Math.round(this.fps)); 8.32 - out.setTimeScale(1); 8.33 - out.setVideoDimension(width, height); 8.34 - this.videoReady = true; 8.35 - } 8.36 + public void initVideo (){ 8.37 + frame = new BufferedImage( 8.38 + width, height, 8.39 + BufferedImage.TYPE_INT_RGB); 8.40 + out.setFrameRate((int) Math.round(this.fps)); 8.41 + out.setTimeScale(1); 8.42 + out.setVideoDimension(width, height); 8.43 + this.videoReady = true; 8.44 + } 8.45 8.46 - public void record(BufferedImage rawFrame) { 8.47 - if (!videoReady){initVideo();} 8.48 - this.frame.getGraphics().drawImage(rawFrame, 0, 0, null); 8.49 - try {out.writeFrame(frame);} 8.50 - catch (IOException e){e.printStackTrace();} 8.51 - } 8.52 + public void record(BufferedImage rawFrame) { 8.53 + if (!videoReady){initVideo();} 8.54 + this.frame.getGraphics().drawImage(rawFrame, 0, 0, null); 8.55 + try {out.writeFrame(frame);} 8.56 + catch (IOException e){e.printStackTrace();} 8.57 + } 8.58 8.59 - public void finish() { 8.60 - try {out.close();} 8.61 - catch (IOException e) {e.printStackTrace();} 8.62 - } 8.63 + public void finish() { 8.64 + try {out.close();} 8.65 + catch (IOException e) {e.printStackTrace();} 8.66 + } 8.67 8.68 8.69
9.1 --- a/src/com/aurellem/capture/video/AbstractVideoRecorder.java Mon Oct 31 07:43:44 2011 -0700 9.2 +++ b/src/com/aurellem/capture/video/AbstractVideoRecorder.java Thu Nov 03 16:00:46 2011 -0700 9.3 @@ -19,9 +19,9 @@ 9.4 import com.jme3.util.Screenshots; 9.5 9.6 /** 9.7 - * <code>VideoProcessor</code> copies the frames it receives to video. 9.8 + * <code>VideoRecorder</code> copies the frames it receives to video. 9.9 * To ensure smooth video at a constant framerate, you should set your 9.10 - * application's timer to a new {@link IsoTimer}. This class will 9.11 + * application's timer to a new <code>IsoTimer</code>. This class will 9.12 * auto-determine the framerate of the video based on the time difference 9.13 * between the first two frames it receives, although you can manually set 9.14 * the framerate by calling <code>setFps(newFramerate)</code>. Be sure to 9.15 @@ -44,7 +44,7 @@ 9.16 */ 9.17 9.18 public abstract class AbstractVideoRecorder 9.19 - implements SceneProcessor, IVideoRecorder, AppState{ 9.20 + implements SceneProcessor, VideoRecorder, AppState{ 9.21 9.22 final File output; 9.23 Camera camera;
10.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 10.2 +++ b/src/com/aurellem/capture/video/FileVideoRecorder.java Thu Nov 03 16:00:46 2011 -0700 10.3 @@ -0,0 +1,41 @@ 10.4 +package com.aurellem.capture.video; 10.5 + 10.6 +import java.awt.image.BufferedImage; 10.7 +import java.io.File; 10.8 +import java.io.IOException; 10.9 +import javax.imageio.ImageIO; 10.10 + 10.11 +public class FileVideoRecorder extends AbstractVideoRecorder{ 10.12 + int current; 10.13 + File outDir; 10.14 + String formatName = "png"; 10.15 + 10.16 + public FileVideoRecorder(File output) throws IOException { 10.17 + super(output); 10.18 + if (output.exists() 10.19 + && output.isDirectory() 10.20 + && (0 == output.listFiles().length)){ 10.21 + // good 10.22 + } 10.23 + else if (!output.exists()){ 10.24 + output.mkdir(); 10.25 + } 10.26 + else { 10.27 + throw new IOException("argument must be either an empty " + 10.28 + "directory or a nonexistent one."); 10.29 + } 10.30 + this.outDir = output; 10.31 + } 10.32 + 10.33 + public void record(BufferedImage rawFrame) { 10.34 + String name = String.format("%07d.%s" , current++, formatName); 10.35 + File target = new File(output, name); 10.36 + try {ImageIO.write(rawFrame, formatName, target);} 10.37 + catch (IOException e) {e.printStackTrace();} 10.38 + } 10.39 + 10.40 + public void finish() {} 10.41 +} 10.42 + 10.43 + 10.44 +
11.1 --- a/src/com/aurellem/capture/video/IVideoRecorder.java Mon Oct 31 07:43:44 2011 -0700 11.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 11.3 @@ -1,21 +0,0 @@ 11.4 -package com.aurellem.capture.video; 11.5 - 11.6 -import java.awt.image.BufferedImage; 11.7 - 11.8 -public interface IVideoRecorder{ 11.9 - 11.10 - void record(BufferedImage image); 11.11 - 11.12 - void pause(); 11.13 - 11.14 - void start(); 11.15 - 11.16 - /** 11.17 - * closes the video file, writing appropriate headers, trailers, etc. 11.18 - * After this is called, no more recording can be done. 11.19 - */ 11.20 - void finish(); 11.21 - 11.22 -} 11.23 - 11.24 -
12.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 12.2 +++ b/src/com/aurellem/capture/video/VideoRecorder.java Thu Nov 03 16:00:46 2011 -0700 12.3 @@ -0,0 +1,29 @@ 12.4 +package com.aurellem.capture.video; 12.5 + 12.6 +import java.awt.image.BufferedImage; 12.7 + 12.8 +public interface VideoRecorder{ 12.9 + 12.10 + /** 12.11 + * Write this image to video, disk, etc. 12.12 + * @param image the image to write 12.13 + */ 12.14 + void record(BufferedImage image); 12.15 + 12.16 + /** 12.17 + * stop recording temporarally. The recording can be started again 12.18 + * with start() 12.19 + */ 12.20 + void pause(); 12.21 + 12.22 + /** 12.23 + * start the recording. 12.24 + */ 12.25 + void start(); 12.26 + 12.27 + /** 12.28 + * closes the video file, writing appropriate headers, trailers, etc. 12.29 + * After this is called, no more recording can be done. 12.30 + */ 12.31 + void finish(); 12.32 +} 12.33 \ No newline at end of file