/**
* Copyright (c) 2009 Andrei Sochirca, All Rights Reserved
*
* 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.
*/
import com.smaxe.os.jna.win32.support.IVideoFrameProcessor;
import com.smaxe.os.jna.win32.support.VideoCaptureLibrary;
import com.smaxe.os.jna.win32.support.VideoCaptureDevice;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.W32API.*;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.VolatileImage;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
/**
* <code>ExVideoCapturer</code> - video capturer.
*
* @author Andrei Sochirca
*/
public final class ExVideoCapturer extends Object
{
/**
* Entry point.
*
* @param args
*/
public static void main(final String[] args)
{
final JVideoScreen screen = new JVideoScreen(false /*flip*/);
final JFrame frame = new JFrame();
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(screen);
frame.pack();
frame.setVisible(true);
List<VideoCaptureDevice> devices = VideoCaptureLibrary.findAllVideoCaptureDevices();
if (devices.isEmpty())
{
System.out.println("Video device is not found");
return;
}
else
{
System.out.println("Available video devices are: ");
for (VideoCaptureDevice device : devices)
{
System.out.println(device.getIndex() + " " + device.getName() + " " + device.getVersion() + "");
}
}
VideoCaptureDevice device = devices.get(0);
device.setFrameFlip(true /*flip*/);
int width = 320, height = 240;
// int width = 640, height = 480;
device.startVideoCapture(new HWND(Native.getComponentPointer(frame)), width, height, new IVideoFrameProcessor()
{
public void onFrame(final int width, final int height, final byte[] rgb, int components)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
screen.setFrameSize(width, height);
screen.setFrame(rgb);
}
});
}
});
}
/**
* <code>JVideoScreen</code> - video screen class.
*/
public final static class JVideoScreen extends JComponent
{
private static final long serialVersionUID = 2397302975258684887L;
/**
* Returns default frame size.
*
* @return default frame size
*/
public final static Dimension getDefaultFrameSize()
{
return DEFAULT_FRAME_SIZE;
}
// introduced for the performance reasons
/**
* Default frame size.
*/
private final static Dimension DEFAULT_FRAME_SIZE = new Dimension(320, 240);
/**
* Default color model.
*/
private final static ColorModel DEFAULT_COLOR_MODEL =
new ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_sRGB),
new int[] {8,8,8},
false, false,
Transparency.OPAQUE,
DataBuffer.TYPE_BYTE);
/**
* RGB bands offset
*/
private final static int BAND_OFFSETS[] = new int[] {
2 /*offset to Blue*/,
1 /*offset to Green*/,
0 /*offset to Red*/
}; // BAND_OFFSETS.length represents # of bands
/**
* <code>ZERO_POINT</code> = new Point(0,0)
*/
private final static Point ZERO_POINT = new Point(0,0);
// fields
// flag that controls whether or not the off-screen buffer is used
private boolean useBuffer = true;
// buffer for the rendered component
private VolatileImage buffer = null;
// height of the component buffer
private int bufferHeight = 0;
// width of the component buffer
private int bufferWidth = 0;
// frame
private Image videoFrame = null;
private Dimension frameSize = null;
private ColorModel colorModel = DEFAULT_COLOR_MODEL;
private SampleModel sampleModel = null;
//
private Dimension screenSize = getDefaultFrameSize();
// image affine transform
private AffineTransform transform = null;
// image transform
private boolean flip = false;
private double scale = 1;
/**
* Constructor.
*/
public JVideoScreen()
{
this(false);
}
/**
* Constructor.
*
* @param flip set <code>true</code> to flip the frame
*/
public JVideoScreen(final boolean flip)
{
this(getDefaultFrameSize(), flip);
}
/**
* Constructor.
*
* @param size video frame size
* @param flip set <code>true</code> to flip the frame
*/
public JVideoScreen(final Dimension size, final boolean flip)
{
setPreferredSize(getDefaultFrameSize());
setFrameSize(size);
flipFrame(flip);
}
/**
* Returns the frame size.
*
* @return frame size
*/
public final Dimension getFrameSize()
{
return frameSize;
}
/**
* Set <code>true</code> to flip video frame vertically.
*
* @param flip flip parameter
*/
public void flipFrame(final boolean flip)
{
this.flip = flip;
this.transform = createTransform(this.frameSize.height, this.flip, this.scale);
}
/**
* Sets video scale.
*
* @param scale scale parameter
*/
public void setScale(final double scale)
{
this.scale = scale;
this.transform = createTransform(this.frameSize.height, this.flip, this.scale);
// layout component if necessary
setScreenSize(this.frameSize);
}
/**
* Sets the frame to be rendered.
*
* @param data frame data
*/
public void setFrame(final byte[] data)
{
setFrame(renderFrame(data));
}
/**
* Sets still image to be shown.
*
* @param image image to be shown
*/
public void setImage(final Image image)
{
setFrame(image);
}
/**
* Sets the video frame.
*
* @param videoFrame video frame
*/
private void setFrame(final Image videoFrame)
{
if (videoFrame == null) return;
this.videoFrame = videoFrame;
setFrameSize(this.videoFrame.getWidth(null), this.videoFrame.getHeight(null));
repaint();
}
/**
* Sets the frame size.
*
* @param size a new screen size to be set
*/
public void setFrameSize(final Dimension size)
{
if (this.frameSize == size) return;
this.frameSize = (size == null) ? getDefaultFrameSize() : size;
// create sample model
this.sampleModel = createSampleModel(this.frameSize);
// create flip transform
this.transform = (this.transform == null) ? null : createTransform(this.frameSize.height, this.flip, this.scale);
this.setScreenSize(this.frameSize);
}
/**
* Sets frame size.
*
* @param width frame width
* @param height frame height
*/
public void setFrameSize(final int width, final int height)
{
if ((frameSize.width != width && width > 0) ||
(frameSize.height != height && height > 0))
{
setFrameSize(new Dimension(width, height));
}
}
/**
* Sets {@link ColorModel <tt>colorModel</tt>} to be used for
* video frame rendering.
*
* @param colorModel color model for frame rendering
*/
public void setFrameColorModel(final ColorModel colorModel)
{
this.colorModel = (colorModel == null) ? DEFAULT_COLOR_MODEL : colorModel;
}
// inner use methods
@Override
public void paintComponent(Graphics g)
{
final int width = getWidth();
final int height = getHeight();
if (useBuffer)
{
// do we need to resize the buffer?
if ((buffer == null) || (bufferWidth != width) || (bufferHeight != height))
{
bufferWidth = width;
bufferHeight = height;
buffer = createBuffer();
}
do
{
switch (buffer.validate(getGraphicsConfiguration()))
{
case VolatileImage.IMAGE_INCOMPATIBLE:
{
buffer = createBuffer();
} // no break here
case VolatileImage.IMAGE_OK:
case VolatileImage.IMAGE_RESTORED:
{
// render offscreen
do
{
Graphics bg = buffer.createGraphics();
render(bg, bufferWidth, bufferHeight);
bg.dispose();
}
while (buffer.contentsLost());
} break;
}
g.drawImage(buffer, 0, 0, this);
}
while (buffer.contentsLost());
}
else
{
render(g, width, height);
}
}
@Override
public void setVisible(final boolean flag)
{
super.setVisible(flag);
}
@Override
public void update(Graphics g)
{
paint(g);
}
// inner use methods
protected void render(Graphics g, final int width, final int height)
{
final int x = (getBounds().width - screenSize.width) / 2;
final int y = (getBounds().height - screenSize.height) / 2;
final Graphics2D g2d = (Graphics2D) g;
// draw background
if (x != 0 || y != 0)
{
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getBounds().width , getBounds().height);
}
if (videoFrame != null)
{
g2d.drawImage(videoFrame, transform, null);
}
}
/**
* Creates buffer.
*
* @return buffer image
*/
private final VolatileImage createBuffer()
{
if (buffer != null) buffer.flush();
return createVolatileImage(bufferWidth, bufferHeight);
}
/**
* Sets screen size by frame size (screen size = frame size * scale).
*
* @param size frame size
*/
private void setScreenSize(final Dimension size)
{
final Dimension screenSize =
new Dimension((int) (scale * size.width), (int) (scale * size.height));
// update component preferred size
final Dimension newPreferredSize =
(screenSize.width > getDefaultFrameSize().width ||
screenSize.height > getDefaultFrameSize().height) ?
screenSize : getDefaultFrameSize();
if (!screenSize.equals(newPreferredSize))
{
this.screenSize = screenSize;
setMaximumSize(screenSize);
setMinimumSize(screenSize);
setPreferredSize(screenSize);
setSize(screenSize);
invalidate();
}
repaint();
}
/**
* Renders video frame (@link BufferedImage) from frame data array,
* frame size, color model.
*
* @param data video frame data
* @return video frame image
*/
private BufferedImage renderFrame(final byte[] data)
{
return new BufferedImage(colorModel,
Raster.createWritableRaster(sampleModel, new DataBufferByte(data,
frameSize.width * frameSize.height * 3, 0), ZERO_POINT),
false, null);
}
/**
* Creatres {@link SampleModel} for the video frame.
* <p> Note: Uses frame size, band offsets...
*
* @return video frame sample model
*/
private final static SampleModel createSampleModel(final Dimension size)
{
return new PixelInterleavedSampleModel(
DataBuffer.TYPE_BYTE /*data type for storing samples*/,
size.width /*width in pixels*/,
size.height /*height in pixels*/,
3 /*pixel stride*/,
size.width * 3 /*line stride*/,
BAND_OFFSETS /*the offsets of all bands*/);
}
/**
* Creates image {@link AffineTransform}.
*
* @param frameHeight frame height
* @param flip <code>true</code> to flip image
* @param scale scale parameter
*
* @return image affine transform
*/
private static AffineTransform createTransform(
final int frameHeight, final boolean flip, final double scale)
{
AffineTransform transform = null;
if (scale == 1)
{
if (!flip) return null;
transform = new AffineTransform();
transform.translate(0, frameHeight);
transform.scale(1, (flip ? -1 : 1));
}
else
{
transform = new AffineTransform();
if (flip) transform.translate(0, scale * frameHeight);
transform.scale(scale, (flip ? -1 : 1) * scale);
}
return transform;
}
}
}