mirror of
https://github.com/processing/processing4.git
synced 2026-02-04 06:09:17 +01:00
370 lines
11 KiB
Java
370 lines
11 KiB
Java
/**
|
|
* Part of the GSVideo library: http://gsvideo.sourceforge.net/
|
|
* Copyright (c) 2008-11 Andres Colubri
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation, version 2.1.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General
|
|
* Public License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
|
* Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
package codeanticode.gsvideo;
|
|
|
|
import processing.core.*;
|
|
|
|
import java.io.File;
|
|
import java.nio.ByteBuffer;
|
|
|
|
import org.gstreamer.Buffer;
|
|
import org.gstreamer.elements.RGBDataFileSink;
|
|
|
|
/**
|
|
* This class makes movies from a running program.
|
|
*/
|
|
public class GSMovieMaker {
|
|
protected PApplet parent;
|
|
protected boolean recording;
|
|
protected RGBDataFileSink recorder;
|
|
protected int width, height;
|
|
|
|
public static final int THEORA = 0;
|
|
public static final int XVID = 1;
|
|
public static final int X264 = 2;
|
|
public static final int DIRAC = 3;
|
|
public static final int MJPEG = 4;
|
|
public static final int MJPEG2K = 5;
|
|
|
|
public static final int WORST = 0;
|
|
public static final int LOW = 1;
|
|
public static final int MEDIUM = 2;
|
|
public static final int HIGH = 3;
|
|
public static final int BEST = 4;
|
|
|
|
/**
|
|
* Constructor that sets the codec to THEORA, MEDIUM quality and 30 fps.
|
|
*
|
|
*/
|
|
public GSMovieMaker(PApplet parent, int requestWidth, int requestHeight,
|
|
String filename) {
|
|
init(parent, requestWidth, requestHeight, filename, THEORA, MEDIUM, 30);
|
|
}
|
|
|
|
/**
|
|
* Constructor that allows to set codec type and fps.
|
|
*
|
|
*/
|
|
public GSMovieMaker(PApplet parent, int requestWidth, int requestHeight,
|
|
String filename, int codecType, int ifps) {
|
|
init(parent, requestWidth, requestHeight, filename, codecType, MEDIUM, ifps);
|
|
}
|
|
|
|
/**
|
|
* Constructor that allows to set codec type, encoding quality and fps.
|
|
*
|
|
*/
|
|
public GSMovieMaker(PApplet parent, int requestWidth, int requestHeight,
|
|
String filename, int codecType, int codecQuality, int ifps) {
|
|
init(parent, requestWidth, requestHeight, filename, codecType,
|
|
codecQuality, ifps);
|
|
}
|
|
|
|
/**
|
|
* Constructor that allows to set the gstreamer encoder and muxer by name.
|
|
* Properties for encoder and muxer are left to wherever the default values are.
|
|
*
|
|
*/
|
|
public GSMovieMaker(PApplet parent, int requestWidth, int requestHeight,
|
|
String filename, String encoder, String muxer, int ifps) {
|
|
init(parent, requestWidth, requestHeight, filename, encoder, muxer, null, null, ifps);
|
|
}
|
|
|
|
/**
|
|
* Constructor that allows to set the gstreamer encoder and muxer by name, as
|
|
* well as the properties.
|
|
*
|
|
*/
|
|
public GSMovieMaker(PApplet parent, int requestWidth, int requestHeight,
|
|
String filename, String encoder, String muxer, String[] propNames, Object[] propValues, int ifps) {
|
|
init(parent, requestWidth, requestHeight, filename, encoder, muxer, propNames, propValues, ifps);
|
|
}
|
|
|
|
/**
|
|
* Releases the gstreamer resources associated to this movie maker object.
|
|
* It shouldn't be used after this.
|
|
*/
|
|
public void delete() {
|
|
recorder.stop();
|
|
recorder.dispose();
|
|
}
|
|
|
|
/**
|
|
* Same as delete.
|
|
*/
|
|
public void dispose() {
|
|
delete();
|
|
}
|
|
|
|
/**
|
|
* Adds a new frame to the video being recorded..
|
|
*
|
|
* @param pixels
|
|
* int[]
|
|
*/
|
|
public void addFrame(int[] pixels) {
|
|
if (recording && pixels.length == width * height) {
|
|
Buffer srcBuffer = new Buffer(width * height * 4);
|
|
|
|
ByteBuffer tmpBuffer = srcBuffer.getByteBuffer();
|
|
tmpBuffer.clear();
|
|
tmpBuffer.asIntBuffer().put(pixels);
|
|
|
|
recorder.pushRGBFrame(srcBuffer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Starts recording.
|
|
*
|
|
*/
|
|
public void start() {
|
|
recorder.start();
|
|
recording = true;
|
|
}
|
|
|
|
/**
|
|
* Finishes recording.
|
|
*
|
|
*/
|
|
public void finish() {
|
|
recording = false;
|
|
recorder.stop();
|
|
}
|
|
|
|
/**
|
|
* Returns the number of frames currently in the pre-encoding queue,
|
|
* waiting to be encoded.
|
|
*
|
|
*/
|
|
public int getQueuedFrames() {
|
|
return recorder.getNumQueuedFrames();
|
|
}
|
|
|
|
/**
|
|
* Returns the number of frames dropped until now.
|
|
*
|
|
*/
|
|
public int getDroppedFrames() {
|
|
return recorder.getNumDroppedFrames();
|
|
}
|
|
|
|
/**
|
|
* Sets the maximum size of the pre-encoding and encoding queues.
|
|
* When the encoding queue is full, the frames start to be accumulated
|
|
* in the pre-encoding queue. By setting the size of the pre-encoding
|
|
* queue to zero, it can grow arbitrarily large.
|
|
*
|
|
*/
|
|
public void setQueueSize(int npre, int nenc) {
|
|
recorder.setPreQueueSize(npre);
|
|
recorder.setSrcQueueSize(nenc);
|
|
}
|
|
|
|
/**
|
|
* Returns true or false depending on whether recording is going
|
|
* on right now or not.
|
|
*
|
|
* @returns boolean
|
|
*/
|
|
public boolean isRecording() {
|
|
return recording;
|
|
}
|
|
|
|
protected void init(PApplet iparent, int requestWidth, int requestHeight,
|
|
String filename, int codecType, int codecQuality, int ifps) {
|
|
this.parent = iparent;
|
|
|
|
GSVideo.init();
|
|
|
|
// register methods
|
|
parent.registerDispose(this);
|
|
|
|
width = requestWidth;
|
|
height = requestHeight;
|
|
|
|
String[] propNames = null;
|
|
Object[] propValues = null;
|
|
|
|
String encoder = "";
|
|
String muxer = "";
|
|
|
|
// Determining container based on the filename extension.
|
|
String fn = filename.toLowerCase();
|
|
if (fn.endsWith(".ogg")) {
|
|
muxer = "oggmux";
|
|
} else if (fn.endsWith(".avi")) {
|
|
muxer = "avimux";
|
|
} else if (fn.endsWith(".mov")) {
|
|
muxer = "qtmux";
|
|
} else if (fn.endsWith(".flv")) {
|
|
muxer = "flvmux";
|
|
} else if (fn.endsWith(".mkv")) {
|
|
muxer = "matroskamux";
|
|
} else if (fn.endsWith(".mp4")) {
|
|
muxer = "mp4mux";
|
|
} else if (fn.endsWith(".3gp")) {
|
|
muxer = "gppmux";
|
|
} else if (fn.endsWith(".mpg")) {
|
|
muxer = "ffmux_mpeg";
|
|
} else if (fn.endsWith(".mj2")) {
|
|
muxer = "mj2mux";
|
|
} else {
|
|
parent.die("Unrecognized video container", null);
|
|
}
|
|
|
|
// Configuring encoder.
|
|
if (codecType == THEORA) {
|
|
encoder = "theoraenc";
|
|
|
|
propNames = new String[1];
|
|
propValues = new Object[1];
|
|
|
|
propNames[0] = "quality";
|
|
Integer q = 31;
|
|
if (codecQuality == WORST) {
|
|
q = 0;
|
|
} else if (codecQuality == LOW) {
|
|
q = 15;
|
|
} else if (codecQuality == MEDIUM) {
|
|
q = 31;
|
|
} else if (codecQuality == HIGH) {
|
|
q = 47;
|
|
} else if (codecQuality == BEST) {
|
|
q = 63;
|
|
}
|
|
propValues[0] = q;
|
|
} else if (codecType == DIRAC) {
|
|
encoder = "schroenc";
|
|
|
|
propNames = new String[1];
|
|
propValues = new Object[1];
|
|
|
|
propNames[0] = "quality";
|
|
Double q = 5.0d;
|
|
if (codecQuality == WORST) {
|
|
q = 0.0d;
|
|
} else if (codecQuality == LOW) {
|
|
q = 2.5d;
|
|
} else if (codecQuality == MEDIUM) {
|
|
q = 5.0d;
|
|
} else if (codecQuality == HIGH) {
|
|
q = 7.5d;
|
|
} else if (codecQuality == BEST) {
|
|
q = 10.0d;
|
|
}
|
|
propValues[0] = q;
|
|
} else if (codecType == XVID) {
|
|
encoder = "xvidenc";
|
|
|
|
// TODO: set Properties of xvidenc.
|
|
} else if (codecType == X264) {
|
|
encoder = "x264enc";
|
|
|
|
propNames = new String[2];
|
|
propValues = new Object[2];
|
|
|
|
// The pass property can take the following values:
|
|
// (0): cbr - Constant Bitrate Encoding (default)
|
|
// (4): quant - Constant Quantizer
|
|
// (5): qual - Constant Quality
|
|
// (17): pass1 - VBR Encoding - Pass 1
|
|
// (18): pass2 - VBR Encoding - Pass 2
|
|
// (19): pass3 - VBR Encoding - Pass 3
|
|
propNames[0] = "pass";
|
|
Integer p = 5;
|
|
propValues[0] = p;
|
|
|
|
// When Constant Quality is specified for pass, then
|
|
// the property quantizer is interpreted as the quality
|
|
// level.
|
|
propNames[1] = "quantizer";
|
|
Integer q = 21;
|
|
if (codecQuality == WORST) {
|
|
q = 50;
|
|
} else if (codecQuality == LOW) {
|
|
q = 35;
|
|
} else if (codecQuality == MEDIUM) {
|
|
q = 21;
|
|
} else if (codecQuality == HIGH) {
|
|
q = 15;
|
|
} else if (codecQuality == BEST) {
|
|
q = 1;
|
|
}
|
|
propValues[1] = q;
|
|
|
|
// The bitrate can be set with the bitrate property, which is integer and
|
|
// has range: 1 - 102400. Default: 2048 Current: 2048.
|
|
// This probably doesn't have any effect unless we set pass to cbr.
|
|
} else if (codecType == MJPEG) {
|
|
encoder = "jpegenc";
|
|
|
|
propNames = new String[1];
|
|
propValues = new Object[1];
|
|
|
|
propNames[0] = "quality";
|
|
Integer q = 85;
|
|
if (codecQuality == WORST) {
|
|
q = 0;
|
|
} else if (codecQuality == LOW) {
|
|
q = 30;
|
|
} else if (codecQuality == MEDIUM) {
|
|
q = 50;
|
|
} else if (codecQuality == HIGH) {
|
|
q = 85;
|
|
} else if (codecQuality == BEST) {
|
|
q = 100;
|
|
}
|
|
propValues[0] = q;
|
|
} else if (codecType == MJPEG2K) {
|
|
encoder = "jp2kenc";
|
|
} else {
|
|
parent.die("Unrecognized video codec", null);
|
|
}
|
|
|
|
initRecorder(filename, ifps, encoder, muxer, propNames, propValues);
|
|
}
|
|
|
|
protected void init(PApplet iparent, int requestWidth, int requestHeight, String filename,
|
|
String encoder, String muxer, String[] propNames, Object[] propValues, int ifps) {
|
|
this.parent = iparent;
|
|
|
|
GSVideo.init();
|
|
|
|
// register methods
|
|
parent.registerDispose(this);
|
|
|
|
width = requestWidth;
|
|
height = requestHeight;
|
|
|
|
initRecorder(filename, ifps, encoder, muxer, propNames, propValues);
|
|
}
|
|
|
|
protected void initRecorder(String filename, int ifps, String encoder, String muxer,
|
|
String[] propNames, Object[] propValues) {
|
|
|
|
File file = new File(parent.savePath(filename));
|
|
recorder = new RGBDataFileSink("MovieMaker", width, height, ifps, encoder,
|
|
propNames, propValues, muxer, file);
|
|
recording = false;
|
|
setQueueSize(60, 30);
|
|
}
|
|
}
|