mirror of
https://github.com/processing/processing4.git
synced 2026-02-02 13:21:07 +01:00
moving to new structure
This commit is contained in:
548
java/libraries/video/src/processing/video/Capture.java
Executable file
548
java/libraries/video/src/processing/video/Capture.java
Executable file
@@ -0,0 +1,548 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2004-09 Ben Fry and Casey Reas
|
||||
The previous version of this code was developed by Hernando Barragan
|
||||
|
||||
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; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
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 processing.video;
|
||||
|
||||
import processing.core.*;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
|
||||
import quicktime.*;
|
||||
import quicktime.qd.*;
|
||||
import quicktime.std.*;
|
||||
import quicktime.std.sg.*;
|
||||
import quicktime.util.RawEncodedImage;
|
||||
|
||||
|
||||
/**
|
||||
* Watchin' shit on the telly.
|
||||
*/
|
||||
public class Capture extends PImage implements Runnable {
|
||||
|
||||
// there are more, but these are all we'll provide for now
|
||||
// The useful ref page for <a href="http://developer.apple.com/documentation/Java/Reference/1.4.1/Java141API_QTJ/constant-values.html">quicktime constants</a>
|
||||
static public final int COMPOSITE = StdQTConstants.compositeIn; // 0
|
||||
static public final int SVIDEO = StdQTConstants.sVideoIn; // 1
|
||||
static public final int COMPONENT = StdQTConstants.rgbComponentIn; // 2
|
||||
static public final int TUNER = StdQTConstants.tvTunerIn; // 6
|
||||
|
||||
static public final int NTSC = StdQTConstants.ntscIn;
|
||||
static public final int PAL = StdQTConstants.palIn;
|
||||
static public final int SECAM = StdQTConstants.secamIn;
|
||||
|
||||
// no longer needed because parent field added to PImage
|
||||
//PApplet parent;
|
||||
|
||||
Method captureEventMethod;
|
||||
String name; // keep track for error messages (unused)
|
||||
Thread runner;
|
||||
|
||||
boolean available = false;
|
||||
|
||||
/** Temporary storage for the raw image
|
||||
data read directly from the capture device */
|
||||
public int data[];
|
||||
|
||||
public int dataWidth;
|
||||
public int dataHeight;
|
||||
public int dataRowBytes;
|
||||
|
||||
/** True if this image is currently being cropped */
|
||||
public boolean crop;
|
||||
|
||||
public int cropX;
|
||||
public int cropY;
|
||||
public int cropW;
|
||||
public int cropH;
|
||||
|
||||
public int frameRate;
|
||||
|
||||
public RawEncodedImage raw;
|
||||
public SequenceGrabber capture;
|
||||
|
||||
/** the guy who's doing all the work */
|
||||
public SGVideoChannel channel;
|
||||
|
||||
/** boundary of image at the requested size */
|
||||
protected QDRect qdrect;
|
||||
|
||||
/*
|
||||
static {
|
||||
try {
|
||||
QTSession.open();
|
||||
} catch (QTException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// this doesn't appear to do jack
|
||||
QTRuntimeException.registerHandler(new QTRuntimeHandler() {
|
||||
public void exceptionOccurred(QTRuntimeException e,
|
||||
Object obj, String s, boolean flag) {
|
||||
System.err.println("Problem inside Capture");
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
public Capture(PApplet parent, int requestWidth, int requestHeight) {
|
||||
this(parent, requestWidth, requestHeight, null, 30);
|
||||
}
|
||||
|
||||
public Capture(PApplet parent, int reqWidth, int reqHeight, int frameRate) {
|
||||
this(parent, reqWidth, reqHeight, null, frameRate);
|
||||
}
|
||||
|
||||
public Capture(PApplet parent, int reqWidth, int reqHeight, String name) {
|
||||
this(parent, reqWidth, reqHeight, name, 30);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If 'name' is null or the empty string, it won't set a specific
|
||||
* device, which means that QuickTime will use that last device
|
||||
* used by a QuickTime application.
|
||||
* <P/>
|
||||
* Unfortunately, Apple's QuickTime API uses the name to select devices,
|
||||
* and in some cases there might be cameras with the same name on a machine.
|
||||
* If you ask for a camera of the same name in sequence, you might see if it
|
||||
* just does the right thing and grabs each separate camera in succession.
|
||||
* If that doesn't work, you might try calling settings() which will
|
||||
* bring up the prompt where you can select a capture device.
|
||||
* <P/>
|
||||
* If the following function:
|
||||
* <PRE>public void captureEvent(Capture c)</PRE>
|
||||
* is defined in the host PApplet, then it will be called every
|
||||
* time a new frame is available from the capture device.
|
||||
*/
|
||||
public Capture(final PApplet parent,
|
||||
final int requestWidth, final int requestHeight,
|
||||
final String name, final int frameRate) {
|
||||
// Running on EDT because of weird hang on OS X
|
||||
// http://dev.processing.org/bugs/show_bug.cgi?id=882
|
||||
// QTSession.open() is hanging, not sure why, but it seems to prefer
|
||||
// being run from the EDT. Not sure if that's a mistaken expectation in
|
||||
// QTJava (we hadn't had trouble in the past because we did everything
|
||||
// on the EDT) or if something broken in more recent QTJ. Or (maybe most
|
||||
// likely) we're simply hitting some other threading strangeness, and
|
||||
// using invokeLater() isolates us from that. Which is a nice way of
|
||||
// saying that it's a hack.
|
||||
//SwingUtilities.invokeLater(new Runnable() {
|
||||
// public void run() {
|
||||
init(parent, requestWidth, requestHeight, name, frameRate);
|
||||
//}
|
||||
//});
|
||||
}
|
||||
|
||||
|
||||
public void init(PApplet parent, int requestWidth, int requestHeight,
|
||||
String name, int frameRate) {
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
this.frameRate = frameRate;
|
||||
|
||||
try {
|
||||
QTSession.open();
|
||||
} catch (QTException e) {
|
||||
e.printStackTrace(System.out);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
qdrect = new QDRect(requestWidth, requestHeight);
|
||||
// workaround for bug with the intel macs
|
||||
QDGraphics qdgraphics = null; //new QDGraphics(qdrect);
|
||||
if (quicktime.util.EndianOrder.isNativeLittleEndian()) {
|
||||
qdgraphics = new QDGraphics(QDConstants.k32BGRAPixelFormat, qdrect);
|
||||
} else {
|
||||
qdgraphics = new QDGraphics(QDGraphics.kDefaultPixelFormat, qdrect);
|
||||
}
|
||||
|
||||
capture = new SequenceGrabber();
|
||||
capture.setGWorld(qdgraphics, null);
|
||||
|
||||
channel = new SGVideoChannel(capture);
|
||||
channel.setBounds(qdrect);
|
||||
channel.setUsage(2); // what is this usage number?
|
||||
capture.startPreview(); // maybe this comes later?
|
||||
|
||||
PixMap pixmap = qdgraphics.getPixMap();
|
||||
raw = pixmap.getPixelData();
|
||||
|
||||
/*
|
||||
if (name == null) {
|
||||
channel.settingsDialog();
|
||||
|
||||
} else if (name.length() > 0) {
|
||||
channel.setDevice(name);
|
||||
}
|
||||
*/
|
||||
if ((name != null) && (name.length() > 0)) {
|
||||
channel.setDevice(name);
|
||||
}
|
||||
|
||||
dataRowBytes = raw.getRowBytes();
|
||||
dataWidth = dataRowBytes / 4;
|
||||
dataHeight = raw.getSize() / dataRowBytes;
|
||||
|
||||
if (dataWidth != requestWidth) {
|
||||
crop = true;
|
||||
cropX = 0;
|
||||
cropY = 0;
|
||||
cropW = requestWidth;
|
||||
cropH = requestHeight;
|
||||
}
|
||||
// initialize my PImage self
|
||||
super.init(requestWidth, requestHeight, RGB);
|
||||
|
||||
parent.registerDispose(this);
|
||||
|
||||
try {
|
||||
captureEventMethod =
|
||||
parent.getClass().getMethod("captureEvent",
|
||||
new Class[] { Capture.class });
|
||||
} catch (Exception e) {
|
||||
// no such method, or an error.. which is fine, just ignore
|
||||
}
|
||||
|
||||
runner = new Thread(this);
|
||||
runner.start();
|
||||
|
||||
} catch (QTException qte) {
|
||||
//} catch (StdQTException qte) {
|
||||
//qte.printStackTrace();
|
||||
|
||||
int errorCode = qte.errorCode();
|
||||
if (errorCode == Errors.couldntGetRequiredComponent) {
|
||||
// this can happen when the capture device isn't available
|
||||
// or wasn't shut down properly
|
||||
parent.die("No capture could be found, " +
|
||||
"or the VDIG is not installed correctly.", qte);
|
||||
} else {
|
||||
parent.die("Error while setting up Capture", qte);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
parent.die("Error while setting up Capture", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* True if a frame is ready to be read.
|
||||
* <PRE>
|
||||
* // put this somewhere inside draw
|
||||
* if (capture.available()) capture.read();
|
||||
* </PRE>
|
||||
* Alternatively, you can use captureEvent(Capture c) to notify you
|
||||
* whenever available() is set to true. In which case, things might
|
||||
* look like this:
|
||||
* <PRE>
|
||||
* public void captureEvent(Capture c) {
|
||||
* c.read();
|
||||
* // do something exciting now that c has been updated
|
||||
* }
|
||||
* </PRE>
|
||||
*/
|
||||
public boolean available() {
|
||||
return available;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the video to crop from its original.
|
||||
* <P>
|
||||
* It seems common that captures add lines to the top or bottom
|
||||
* of an image, so this can be useful for removing them.
|
||||
* Internally, the pixel buffer size returned from QuickTime is
|
||||
* often a different size than requested, so crop will be set
|
||||
* more often than not.
|
||||
*/
|
||||
public void crop(int x, int y, int w, int h) {
|
||||
/*
|
||||
if (imageMode == CORNERS) {
|
||||
w -= x; // w was actually x2
|
||||
h -= y; // h was actually y2
|
||||
}
|
||||
*/
|
||||
|
||||
crop = true;
|
||||
cropX = Math.max(0, x);
|
||||
cropY = Math.max(0, y);
|
||||
cropW = Math.min(w, dataWidth);
|
||||
cropH = Math.min(dataHeight, y + h) - cropY;
|
||||
|
||||
// if size has changed, re-init this image
|
||||
if ((cropW != width) || (cropH != height)) {
|
||||
init(w, h, RGB);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the cropping (if any) of the image.
|
||||
* <P>
|
||||
* By default, cropping is often enabled to trim out black pixels.
|
||||
* But if you'd rather deal with them yourself (so as to avoid
|
||||
* an extra lag while the data is moved around) you can shut it off.
|
||||
*/
|
||||
public void noCrop() {
|
||||
crop = false;
|
||||
}
|
||||
|
||||
|
||||
public void read() {
|
||||
//try {
|
||||
//synchronized (capture) {
|
||||
loadPixels();
|
||||
synchronized (pixels) {
|
||||
//System.out.println("read1");
|
||||
if (crop) {
|
||||
//System.out.println("read2a");
|
||||
// f#$)(#$ing quicktime / jni is so g-d slow, calling copyToArray
|
||||
// for the invidual rows is literally 100x slower. instead, first
|
||||
// copy the entire buffer to a separate array (i didn't need that
|
||||
// memory anyway), and do an arraycopy for each row.
|
||||
if (data == null) {
|
||||
data = new int[dataWidth * dataHeight];
|
||||
}
|
||||
raw.copyToArray(0, data, 0, dataWidth * dataHeight);
|
||||
int sourceOffset = cropX + cropY*dataWidth;
|
||||
int destOffset = 0;
|
||||
for (int y = 0; y < cropH; y++) {
|
||||
System.arraycopy(data, sourceOffset, pixels, destOffset, cropW);
|
||||
sourceOffset += dataWidth;
|
||||
destOffset += width;
|
||||
}
|
||||
} else { // no crop, just copy directly
|
||||
//System.out.println("read2b");
|
||||
raw.copyToArray(0, pixels, 0, width * height);
|
||||
}
|
||||
//System.out.println("read3");
|
||||
|
||||
available = false;
|
||||
// mark this image as modified so that PGraphicsJava2D and
|
||||
// PGraphicsOpenGL will properly re-blit and draw this guy
|
||||
updatePixels();
|
||||
//System.out.println("read4");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void run() {
|
||||
while ((Thread.currentThread() == runner) && (capture != null)) {
|
||||
try {
|
||||
synchronized (capture) {
|
||||
capture.idle();
|
||||
//read();
|
||||
available = true;
|
||||
|
||||
if (captureEventMethod != null) {
|
||||
try {
|
||||
captureEventMethod.invoke(parent, new Object[] { this });
|
||||
} catch (Exception e) {
|
||||
System.err.println("Disabling captureEvent() for " + name +
|
||||
" because of an error.");
|
||||
e.printStackTrace();
|
||||
captureEventMethod = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (QTException e) {
|
||||
errorMessage("run", e);
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(1000 / frameRate);
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the frameRate for how quickly new frames are read
|
||||
* from the capture device.
|
||||
*/
|
||||
public void frameRate(int iframeRate) {
|
||||
if (iframeRate <= 0) {
|
||||
System.err.println("Capture: ignoring bad frameRate of " +
|
||||
iframeRate + " fps.");
|
||||
return;
|
||||
}
|
||||
frameRate = iframeRate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called by applets to stop capturing video.
|
||||
*/
|
||||
public void stop() {
|
||||
if (capture != null) {
|
||||
try {
|
||||
capture.stop(); // stop the "preview"
|
||||
} catch (StdQTException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
capture = null;
|
||||
}
|
||||
runner = null; // unwind the thread
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called by PApplet to shut down video so that QuickTime
|
||||
* can be used later by another applet.
|
||||
*/
|
||||
public void dispose() {
|
||||
stop();
|
||||
//System.out.println("calling dispose");
|
||||
// this is important so that the next app can do video
|
||||
QTSession.close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* General error reporting, all corraled here just in case
|
||||
* I think of something slightly more intelligent to do.
|
||||
*/
|
||||
protected void errorMessage(String where, Exception e) {
|
||||
parent.die("Error inside Capture." + where + "()", e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the format to ask for from the video digitizer:
|
||||
* TUNER, COMPOSITE, SVIDEO, or COMPONENT.
|
||||
* <P>
|
||||
* The constants are just aliases to the constants returned from
|
||||
* QuickTime's getInputFormat() function, so any valid constant from
|
||||
* that will work just fine.
|
||||
*/
|
||||
public void source(int which) {
|
||||
try {
|
||||
VideoDigitizer digitizer = channel.getDigitizerComponent();
|
||||
int count = digitizer.getNumberOfInputs();
|
||||
for (int i = 0; i < count; i++) {
|
||||
//System.out.println("format " + digitizer.getInputFormat(i));
|
||||
if (digitizer.getInputFormat(i) == which) {
|
||||
digitizer.setInput(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("The specified source() is not available.");
|
||||
|
||||
} catch (StdQTException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Could not set the video input source.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the video format standard to use on the
|
||||
* video digitizer: NTSC, PAL, or SECAM.
|
||||
* <P>
|
||||
* The constants are just aliases to the constants used for
|
||||
* QuickTime's setInputStandard() function, so any valid
|
||||
* constant from that will work just fine.
|
||||
*/
|
||||
public void format(int which) {
|
||||
try {
|
||||
VideoDigitizer digitizer = channel.getDigitizerComponent();
|
||||
digitizer.setInputStandard(which);
|
||||
} catch (StdQTException e) {
|
||||
e.printStackTrace();
|
||||
//throw new RuntimeException("Could not set the video input format");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the settings dialog for this input device.
|
||||
*/
|
||||
public void settings() {
|
||||
try {
|
||||
// fix for crash here submitted by hansi (stop/startPreview lines)
|
||||
capture.stop();
|
||||
|
||||
// Whenever settingsDialog() is called, the boundries change,
|
||||
// causing the image to be cropped. Fix for Bug #366
|
||||
// http://dev.processing.org/bugs/show_bug.cgi?id=366
|
||||
channel.setBounds(qdrect);
|
||||
|
||||
// Open the settings dialog (throws an Exception if canceled)
|
||||
channel.settingsDialog();
|
||||
|
||||
} catch (StdQTException qte) {
|
||||
int errorCode = qte.errorCode();
|
||||
if (errorCode == Errors.userCanceledErr) {
|
||||
// User only canceled the settings dialog, continue as we were
|
||||
} else {
|
||||
qte.printStackTrace();
|
||||
throw new RuntimeException("Error inside Capture.settings()");
|
||||
}
|
||||
}
|
||||
try {
|
||||
// Start the preview again (unreachable if newly thrown exception)
|
||||
capture.startPreview();
|
||||
} catch (StdQTException qte) {
|
||||
qte.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of all available captures as a String array.
|
||||
* i.e. println(Capture.list()) will show you the goodies.
|
||||
*/
|
||||
static public String[] list() {
|
||||
try {
|
||||
QTSession.open();
|
||||
SequenceGrabber grabber = new SequenceGrabber();
|
||||
SGVideoChannel channel = new SGVideoChannel(grabber);
|
||||
|
||||
SGDeviceList deviceList = channel.getDeviceList(0); // flags is 0
|
||||
String listing[] = new String[deviceList.getCount()];
|
||||
for (int i = 0; i < deviceList.getCount(); i++) {
|
||||
listing[i] = deviceList.getDeviceName(i).getName();
|
||||
}
|
||||
// properly shut down the channel so the app can use it again
|
||||
grabber.disposeChannel(channel);
|
||||
QTSession.close();
|
||||
return listing;
|
||||
|
||||
} catch (QTException qte) {
|
||||
int errorCode = qte.errorCode();
|
||||
if (errorCode == Errors.couldntGetRequiredComponent) {
|
||||
throw new RuntimeException("Couldn't find any capture devices, " +
|
||||
"read the video reference for more info.");
|
||||
} else {
|
||||
qte.printStackTrace();
|
||||
throw new RuntimeException("Problem listing capture devices, " +
|
||||
"read the video reference for more info.");
|
||||
}
|
||||
}
|
||||
//return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user