 |
Answers to common questions, instructions for common projects, and solutions to common issues that spring up when working with JMF
Table of Contents
Topics
Getting Started
@author captfoss
Whenever you first get started with JMF, it can be a daunting framework to try to wrap your head around. The biggest obsticle for a new JMF user is understanding what all of the peices +do+, how they work together, etc...
If you really want to learn to be a good JMF programmer, you must first familiarize yourself with all of the peices, aka, the main JMF components. You should read JMF Component Overview : Main Components which is part of this guide, and just come to understand what the peices are designed to do.
Second, you need to go read through some example code. It is helpful to just read through the sample code and, using what you have learned about the components, see how they do their job and work together. If you know what all of the peices do, then JMF sample code gets a lot less confusing.
- One of the best pages to find example code with good instructions on how to run it is a class website from the Department of Computer Science at the Old Dominion University.
- Their site also contains an index of sample material that can be useful too.
- And, if you prefer a university-style approach to learning, they have a series of lectures that are worth reading.
Once you have a good grasp of those examples, you might want to continue on with some of the more complex examples put out directly by Sun. They are all designed to be utility programs to show you how to perform specific tasks, but they are also helpful learning tools as well. Most users will only study the sample code that directly relates to their first project, and that is a good way to get your feet wet. Before digging into those, you should read JMF Component Overview : Specialized Components which is part of this guide, and just come to understand what the more specialized peices are designed to do.
Resources
@author captfoss
The following is a list of links that any JMF programmer should have readily available to them.
- Java SE 6 API
- JMF 2.0 API
- Official Sun JMF F.A.Q.
- JMF 2.1.1E Supported Format List
- ODU Examples Page
- Official Sun JMF 2.1.1 Solutions Page
- Official Sun JMF Forum
- Java.net JMFJavapedia Article
- Java.net JMF Quickstart Guide Javapedia Article
JMF Component Overview : Main Components
@author captfoss
JMF is a component-based architecture designed to allow you to capture audio/video streams, change the format (transcode) of a/v streams, transmit and receive a/v streams over RTP and RTSP, and save the streams to disk.
Here is a list of the most-commonly used components of JMF.
MediaLocator
JMF 2.0 API
A MediaLocator is simply a URL that points to your media.
MediaLocator Examples
DataSource
JMF 2.0 API
A DataSource is an object whose job is to transfer data from one component to another. You create a DataSource by telling it where its data will come from, and then you give to whatever object will be reading data from it.
DataSource Examples
Player
JMF 2.0 API
A Player object is used to output a media stream for the user to see.
Player Examples
Processor
JMF 2.0 API
A Processor is an object whose job is to take a media stream and do work on it. The work can include encoding, decoding, multiplexing, demultiplexing, modifying the data via custom effect plugins, and rendering the data. The processor itself doesn't modify the data in any way, but passes it through the appropriate Plugin objects to perform all of the requested functions.
Processors accept data from a DataSource, and output data to a DataSource (or, if they use a custom Renderer, there will be no output).
DataSink
JMF 2.0 API
A DataSink is an object whose job is to export / save the data from a DataSource. You create a DataSink by giving it an input DataSource and a MediaLocator of where you want it to save the data.
DataSink Examples
Manager
JMF 2.0 API
Whenever you create an +instance+ of a DataSource/Player/Processor/DataSink, you are not getting an instance of that actual class, but rather an instance of a subclass that is specially designed to work on the kind of data you're working with. For instance, when you create a DataSource to fetch an RTP stream, it's a different subclass of DataSource than you'd get if you wanted to fetch a file from the disk. This certainly makes sense from a +design perspective+ but would certainly be +too complex+ if the user was required to select the appropriate class themself.
To make all of this transparent to the user, the Manager class has several static methods that are designed to create and return the appropriate subclass of DataSource, Player, Processor, and DataSink, based on the information you have to pass in.
Manager Examples
RTPManager
JMF 2.0 API
An RTPManager object is used to create, start, run, and close an RTP session.
DataSinks and DataSources can be used to send and receive RTP streams, but an RTPManager allows a finer level of control, allowing you to do things like send to multiple destinations, respond to specific RTP events, or use a custom transport mechanism to deliver the RTP packets.
CaptureDeviceManager
JMF 2.0 API
Maintains the list of capture devices that were present at the last scan for devices, and allows your program to lookup the proper MediaLocator to use to connect to the capture devices.
- Utility program that will iterate through all of the supported media formats and display any capture devices on the system that support capturing in that format.
JMF Component Overview : Specialized Components
@author captfoss
A lot of the power of JMF is not in its implementation but in its ease of extensibility.
Here is a list of the components that are commonly used to extend JMF's functionality.
Codec
JMF 2.0 API
Codecs are plugina that you can register with the PluginManager that will allow a Processor or to encode and/or decode new media formats. Specifically, they are designed to take in media in one format and return it in another (but there is no technical requirement that they do so).
Effect
JMF 2.0 API
Effects are plugins that you can register with the PluginManager that will allow you to modify a data stream (frame by frame) as it passes through a Processor. Specificlly, they are designed to take in media in one format, modify it in some way, and return it in the same format (but there is no technical requirement that they do so).
Multiplexer
JMF 2.0 API
Multiplexers are plugins that you can register with the PluginManager that will allow you to interleave several streams of data (technically, several "tracks" of data) into a single stream (track) as they pass through a Processor.
Demultiplexer
JMF 2.0 API
Demultiplexers are plugins that you can register with the PluginManager that will allow you to recover several streams of data (technically, several "tracks" of data) from a single stream (track) as it passes through a Processor.
Buffer
JMF 2.0 API
The Buffer object is a container object that JMF uses to store a "chunk" of data and transfer it from place to place. Buffer objects often contain a single video frame, and can be converted into an AWT Image object using the BufferToImage class.
RTPConnector
JMF 2.0 API
By default, JMF will use UDP sockets as the transport mechanism for RTP communication. Implementing a custom RTPConnector will allow you to use a different transport mechanism, to allow you to do things like stream RTP over Bluetooth or IP sockets.
PlugInManager
JMF 2.0 API
All operations that JMF can do on a media stream (encoding, decoding, multiplexing, demultiplexing, rendering, custom effects) are implemented as Plugin objects. The PluginManager is a repository of Plugins that the Manager class and Processor classes query to determine (1) if a requested operation is supported (2) which Plugin it needs to create to perform said operation.
From the perspective of the user, you must register your custom Plugins (Codecs, Effects, Multiplexers, Demultiplexers, Renderers) with the PluginManager in order for the system to utilize them.
Q: My DataSource takes about 10 seconds to startup. What's wrong?
@author captfoss
I issued the following command to the JVM. Sample.wav is on my harddrive so this should be faster than 10 seconds...
java AVTransmit2 file://C:/sample.wav 192.168.0.101 22222
Why is it so slow? Did I do something wrong?
JMF is designed around the concept of components that know what they support, and it finds the correct component to use by looping through all of the components and asking each "Do you support this?". If the correct component to use is 15th down the list, your code might be a lot slower if the first 14 components need to do a long check to see if they support something.
In this case, the MediaLocator is built with the prefix "file://", which, because of the double-slash(//), will make the network components try to connect to that over the internet. Those will time out after about 10 seconds.
The appropriate prefix is "file:/", so the correct command is:
java AVTransmit2 file:/C:/sample.wav 192.168.0.101 22222
Q: My DataSink isn't working correctly. What's wrong?
@author captfoss
It's often helpful to think about JMF in terms of water(data) flowing through pipes(JMF components).
In the case of a DataSink, imagine that your DataSink is an empty bottle of water you want to fillup at a faucet, and imagine you don't want to waste any water (sink and bottle should both be dry afterwards). Realistic goals, right?
You would want to make sure your bottle is opened and placed under the faucet before you turn on the faucet. You'd also want to stop the faucet before you move the bottle of water, and you'd want to put the cap back on the bottle of water immediately after moving it away from the faucet. These are actually the same steps to follow to make your DataSink work correctly!
In order for a DataSink to output a valid file, there is a specific set of steps you must follow. Assuming your Processor is setup correctly, you need to do the following steps in order...
- Do all of the necessary setup for your Processor (get it to the realized state and ready to be started, but don't start it)
- Create your DataSink
- Open your DataSink +(open the bottle)+
- Start your DataSink +(put the bottle under the faucet)+
- Start your Processor +(start the faucet)+
- +<...wait for however long while your video is recording...>+
- Stop your Processor +(stop the faucet)+
- Stop your DataSink +(remove the bottle from under the faucet)+
- Close your DataSink +(put the cap back on your bottle)+
Q: How can I change the resolution of my web cam capture?
@author captfoss
Here's a sample function to allow you to change to any (supported) format.
public boolean requestCaptureFormat(Format requested_format, DataSource ds) {
if (ds instanceof CaptureDevice) {
FormatControl[] fcs = ((CaptureDevice) ds).getFormatControls();
for (FormatControl fc : fcs) {
Format[] formats = ((FormatControl) fc).getSupportedFormats();
for (Format format : formats) {
if (requested_format.matches(format)) {
((FormatControl) fc).setFormat(format);
return true;
}
}
}
}
return false;
}
If you'd like a fully-functional utility program, you can also look at the
Q: Can I capture from JavaSound directly?
@author captfoss
Yes, it's possible to capture directly from JavaSound by creating a JavaSound DataSource. This would give you access to all of the JavaSound capabilities that JMF doesn't let you have access to when you use the javasound:// MediaLocator.
Q: Why won't JMF play my file? It says it supports it...
@author captfoss
A pretty common complaint amoung new JMF users is, "JMF won't play AVI files", or, "JMF won't play my MOV file"...
JMF does, in fact, play AVI and MOV files. Don't think that If you can't play your AVI that JMF lied about being able to open AVI files. You're probably just using a file encoding that JMF doesn't have a codec for!
Let's have a quick discussion about the difference between file types and file encodings.
- File type
- What you normally associate with a file extension. (IE, AVI, MOV, WAV, MP3, etc...)
- File encodings
- How the actual media data is encoded. (IE, RGB, YUV, MJPEG, Cinepak, MPEG, DivX...)
When JMF says it supports a file format, that just means it has the ability to open the file and parse the file header. It doesn't mean it has the ability to decode the actual data inside the file. JMF may not have the codecs it needs to play all kinds of AVIs, for instance. An AVI with a DivX encoding, for instance, won't work with JMF.
http://java.sun.com/javase/technologies/desktop/media/jmf/2.1.1/formats.html
On the supported formats list, unless you're using a file type that JMF knows how to open and a file encoding that JMF supports, your file won't work.
Q: Why doesn't CaptureDeviceManager return devices when run from a JAR?
@author captfoss
I compile my application and run it, and I get the correct list of devices. However, when I then turn my application into a JAR, CaptureDeviceManager.getDeviceList() returns null. What am I doing wrong?
CaptureDeviceManager.getDeviceList() loads the device list from a binary file called the JMF registry. This file is saved as jmf.properties and is located inside your JMF installation. In order for JMF to load the device list, jmf.properties has to be on your classpath.
Essentially, the problem occurs because of the way JAR files work. JAR files don't use your enviromental classpath variables, they have their own internal classpath that overrides whatever you have in your environment. JMF relies on environmental variables that were configured at installation, and the JAR file overrides those environmental variables.
Okay...but how do I fix it?
First, find your jmf.properites file and "write down" the full rooted path to it.
Now, modify your JAR's manifest file so that the folder containing your jmf.properties file is on the internal JAR's classpath.
If you need help setting classpaths in a JAR file, google it.
Q: Why won't the AVTransmit2 example stream to the same machine?
@author captfoss
This issue deals with the way AVTransmit2 was coded to handle RTP transmissions. Let's take a look at some code from the createTransmitter function of AVTransmit2...
localAddr = new SessionAddress( InetAddress.getLocalHost(), port);
destAddr = new SessionAddress( ipAddr, port);
rtpMgrs[i].initialize( localAddr);
rtpMgrs[i].addTarget( destAddr);
If you'll notice the two SessionAddresses, the localAddr is the IP/PORT combination that the program is going to bind to locally to send, and the destAddr is the IP/PORT combination that it will be sending to. Because it's just using the same port number to send from that it's sending to, whenever you run AVReceive2 on the same machine, it won't be able to bind to that port to receive, because it will already be in use.
To fix this issue and allow yourself to send and receive on the same machine, you just need to modify AVTransmit2 not to bind to the destination port number. You could do this a number of ways, obviously, but the best way would just be to have the system pick a free port for you...
localAddr = new SessionAddress( InetAddress.getLocalHost(), SessionAddress.ANY_PORT);
destAddr = new SessionAddress( ipAddr, port);
rtpMgrs[i].initialize( localAddr);
rtpMgrs[i].addTarget( destAddr);
Replace those 4 lines in AVTransmit2 and you can send and receive from the same machine!
|