Introduction
Last year I wrote an article documenting Java class creation and distribution, using a video display and picture capture class as an example. The article concentrated on the mechanics of creating redistributable Java classes rather than video capture, and did not delve deeply into the nuts and bolts of Java Media Framework audio and video capture. So, I wanted to get to know how to use the JMF better. Specifically, I wanted to be able to capture both sight and sound into a file using Java. There was not much information that could be found (vague allusions to clones), so when I finally found a solid explanation and got the code to work, it seemed necessary to share the example.
Background
Java and the Java Media Framework (JMF) offer a powerful yet relatively quick and easy way of developing multimedia applications. But, there is a lot of subtlety in making the platform work. The example presented here shows the basics of finding audio and video devices, creating data sources from these devices, cloning a data source so that it can be used in the player to be displayed and in the processor so it can be captured, and capturing the data as an AVI video file.
Using the Code
The code example is a very basic illustration of capturing sight and sound into a file using Java. To use the code, the JMF has to be installed on the system running it. NetBeans IDE 6.7.1 was used to build the application, but any IDE will work as well.
Merging Video with Audio
In order to capture the video with sound, two data sources are required, but the objects that use data sources only take one data source as an argument. A new data source has to be created using the two original sources. This is done with an array of the data sources to be merged and the javax.media.Manager.createMergingDataSource function:
DataSource mixedDataSource = null;
DataSource dsArray[] = new DataSource[2];
dsArray[0] = CaptureVidDS;
dsArray[1] = audioDataSource;
try {
mixedDataSource =
javax.media.Manager.createMergingDataSource(dsArray);
} catch (Exception e) {
}
The Processor
A processor object is similar to a player. The processor processes data and creates an output in the destination format required. The processor is created using Manager.createRealizedProcessor from the processor model, which itself is created using the data source, data format, and the output type as arguments.
ProcessorModel processorModel =
new ProcessorModel(mixedDataSource, outputFormat, outputType);
try {
processor = Manager.createRealizedProcessor(processorModel);
} catch (Exception e) {
}
The Data Sink
The DataSink object takes a DataSource as input, and renders the output to a specified destination. In this case, the input is the data output of the processor which controls the data sources and the output format. The data sink will control capture to a specific media destination.
DataSource source = processor.getDataOutput();
MediaLocator mediadestination = new MediaLocator("file:.\\vidcap.avi");
dataSink = Manager.createDataSink(source, mediadestination);
dataSinkListener = new TheDataSinkListener();
dataSink.addDataSinkListener(dataSinkListener);
dataSink.open();
The Data Sink Listener
The DataSinkListener is used just like a window listener to control the data sink based on internal events or user actions. In this program's case, the closing of the frame created to display the video causes the processor to stop, which cuts off the data stream to the data sink. The data sink listener allows the program thread to sleep just before the program completely closes, to make sure the JMF threads are complete prior to the program ending. In this case, the sleep is 10 milliseconds.
public void windowClosing(WindowEvent event) {
Frame f = (Frame) event.getSource();
processor.stop();
processor.close();
dataSinkListener.waitEndOfStream(10);
dataSink.close();
player.stop();
player.close();
f.dispose();
System.exit(0);
....
class TheDataSinkListener implements DataSinkListener {
boolean endOfStream = false;
public void dataSinkUpdate(DataSinkEvent event) {
if (event instanceof javax.media.datasink.EndOfStreamEvent) {
endOfStream = true;
}
}
public void waitEndOfStream(long checkTimeMs) {
while (!endOfStream) {
try {
Thread.sleep(checkTimeMs);
} catch (InterruptedException ie) {
}
}
}
The Complete Source Code
package vidcapminimal;
import java.util.Vector;
import java.awt.*;
import java.awt.event.*;
import javax.media.*;
import javax.media.format.*;
import javax.media.protocol.*;
import javax.media.datasink.*;
public class Main {
public static void main(String[] args) {
new VidCap();
}
}
class VidCap {
CaptureDeviceInfo device = null;
MediaLocator ml = null;
Player player = null;
Component videoScreen = null;
Processor processor = null;
DataSink dataSink = null;
TheDataSinkListener dataSinkListener = null;
VidCap() {
try { Vector deviceList = CaptureDeviceManager.getDeviceList(new YUVFormat());
device = (CaptureDeviceInfo) deviceList.firstElement();
ml = device.getLocator();
DataSource ods = null;
ods = Manager.createDataSource(ml);
DataSource cloneableDS = Manager.createCloneableDataSource(ods);
DataSource PlayerVidDS = cloneableDS;
DataSource CaptureVidDS =
((javax.media.protocol.SourceCloneable) cloneableDS).createClone();
player = Manager.createRealizedPlayer(PlayerVidDS);
player.start();
deviceList = CaptureDeviceManager.getDeviceList(
new javax.media.format.AudioFormat(null));
device = (CaptureDeviceInfo) deviceList.firstElement();
ml = device.getLocator();
DataSource audioDataSource = Manager.createDataSource(ml);
DataSource mixedDataSource = null;
DataSource dsArray[] = new DataSource[2];
dsArray[0] = CaptureVidDS; dsArray[1] = audioDataSource;
try {
mixedDataSource = javax.media.Manager.createMergingDataSource(dsArray);
} catch (Exception e) {
}
FileTypeDescriptor outputType =
new FileTypeDescriptor(FileTypeDescriptor.MSVIDEO);
Format outputFormat[] = new Format[2];
outputFormat[0] = new VideoFormat(VideoFormat.YUV);
outputFormat[1] = new AudioFormat(AudioFormat.LINEAR);
ProcessorModel processorModel =
new ProcessorModel(mixedDataSource, outputFormat, outputType);
try {
processor = Manager.createRealizedProcessor(processorModel);
} catch (Exception e) {
}
try {
DataSource source = processor.getDataOutput();
MediaLocator mediadestination = new MediaLocator("file:.\\vidcap.avi");
dataSink = Manager.createDataSink(source, mediadestination);
dataSinkListener = new TheDataSinkListener();
dataSink.addDataSinkListener(dataSinkListener);
dataSink.open();
dataSink.start();
processor.start();
} catch (Exception e) {
}
videoScreen = player.getVisualComponent();
Frame frm = new Frame("Display of Webcam");
frm.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent event) {
Frame f = (Frame) event.getSource();
processor.stop();
processor.close();
dataSinkListener.waitEndOfStream(10);
dataSink.close();
player.stop();
player.close();
f.dispose();
System.exit(0);
}
});
frm.setBounds(10, 10, 300, 300);
frm.add(videoScreen);
frm.setVisible(true);
} catch (Exception e) {
System.out.println(e);
}
}
}
class TheDataSinkListener implements DataSinkListener {
boolean endOfStream = false;
public void dataSinkUpdate(DataSinkEvent event) {
if (event instanceof javax.media.datasink.EndOfStreamEvent) {
endOfStream = true;
}
}
public void waitEndOfStream(long checkTimeMs) {
while (!endOfStream) {
try {
Thread.sleep(checkTimeMs);
} catch (InterruptedException ie) {
}
}
}
}
Points of Interest
The code was developed and tested using Windows XP. The code was also tested using Ubuntu 9.1, and in Ubuntu, the code runs as is (with changes to the file name path possibly needed), but the JMF would not recognize the camera in my HP 6830s laptop.
History
- 1.0: This is the first version of the code simplified for use as an instructive example. If there is interest expressed in this material, I will expand the code to include a snapshot (video frame), capture and control of device, and format selections.
- 1.1: May 26, 2010: I found that the setting of the output file video format to RGB did not work for an older (comes with Windows 98 only software) Philips PCA646VC camera I tested. Changing the code to:
outputFormat[0] = new VideoFormat(VideoFormat.RGB);
outputFormat[0] = new VideoFormat(VideoFormat.YUV);
Also allowed the camera to create a usable video file.