JMF: extracting an image from video

F

Frank Buss

I want to extract an image from a video. I've modified an example from
Sun (see below), but it doesn't work. In the method "processBuffer" (see
at the end of the code) I always get null for the image. What's wrong?

The test video can be played with another program with JMF and the
statistic information are shown with my code.

The code:

import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;

import javax.imageio.*;
import javax.media.*;
import javax.media.datasink.*;
import javax.media.format.*;
import javax.media.protocol.*;
import javax.media.util.*;

/**
* Sample program to read write the first image of a video as a jpg.
*/
public class DataSourceReader implements ControllerListener,
DataSinkListener {

private Processor p;
private Object waitSync = new Object();
private boolean stateTransitionOK = true;

/**
* Given a DataSource, create a processor and hook up the output
* DataSource from the processor to a customed DataSink.
*/
public boolean open(DataSource ds) {
try {
p = Manager.createProcessor(ds);
} catch (Exception e) {
System.err.println(
"Failed to create a processor from the given DataSource: " + e);
return false;
}

p.addControllerListener(this);

// Put the Processor into configured state.
p.configure();
if (!waitForState(Processor.Configured)) {
System.err.println("Failed to configure the processor.");
return false;
}

// Get the raw output from the processor.
p.setContentDescriptor(new ContentDescriptor(ContentDescriptor.RAW));

p.realize();
if (!waitForState(Controller.Realized)) {
System.err.println("Failed to realize the processor.");
return false;
}

// Get the output DataSource from the processor and
// hook it up to the DataSourceHandler.
DataSource ods = p.getDataOutput();
DataSourceHandler handler = new DataSourceHandler();

try {
handler.setSource(ods);
} catch (IncompatibleSourceException e) {
System.err.println(
"Cannot handle the output DataSource from the processor: "
+ ods);
return false;
}

handler.addDataSinkListener(this);
handler.start();

// Prefetch the processor.
p.prefetch();
if (!waitForState(Controller.Prefetched)) {
System.err.println("Failed to prefetch the processor.");
return false;
}

// Start the processor.
p.start();

return true;
}

/**
* Block until the processor has transitioned to the given state.
* Return false if the transition failed.
*/
boolean waitForState(int state) {
synchronized (waitSync) {
try {
while (p.getState() < state && stateTransitionOK)
waitSync.wait();
} catch (Exception e) {
}
}
return stateTransitionOK;
}

/**
* Controller Listener.
*/
public void controllerUpdate(ControllerEvent evt) {

if (evt instanceof ConfigureCompleteEvent
|| evt instanceof RealizeCompleteEvent
|| evt instanceof PrefetchCompleteEvent) {
synchronized (waitSync) {
stateTransitionOK = true;
waitSync.notifyAll();
}
} else if (evt instanceof ResourceUnavailableEvent) {
synchronized (waitSync) {
stateTransitionOK = false;
waitSync.notifyAll();
}
} else if (evt instanceof EndOfMediaEvent) {
p.close();
} else if (evt instanceof SizeChangeEvent) {
}
}

/**
* DataSink Listener
*/
public void dataSinkUpdate(DataSinkEvent evt) {

if (evt instanceof EndOfStreamEvent) {
System.err.println("All done!");
evt.getSourceDataSink().close();
System.exit(0);
}
}

/***************************************************
* Inner class
*
***************************************************/

/**
* This DataSourceHandler class reads from a DataSource and display
* information of each frame of data received.
*/
class DataSourceHandler implements DataSink, BufferTransferHandler {
DataSource source;
PullBufferStream pullStrms[] = null;
PushBufferStream pushStrms[] = null;

// Data sink listeners.
private Vector listeners = new Vector(1);

// Stored all the streams that are not yet finished (i.e. EOM
// has not been received.
SourceStream unfinishedStrms[] = null;

// Loop threads to pull data from a PullBufferDataSource.
// There is one thread per each PullSourceStream.
Loop loops[] = null;

Buffer readBuffer;

/**
* Sets the media source this <code>MediaHandler</code>
* should use to obtain content.
*/
public void setSource(DataSource source)
throws IncompatibleSourceException {

// Different types of DataSources need to handled differently.
if (source instanceof PushBufferDataSource) {

pushStrms = ((PushBufferDataSource) source).getStreams();
unfinishedStrms = new SourceStream[pushStrms.length];

// Set the transfer handler to receive pushed data from
// the push DataSource.
for (int i = 0; i < pushStrms.length; i++) {
pushStrms.setTransferHandler(this);
unfinishedStrms = pushStrms;
}

} else if (source instanceof PullBufferDataSource) {

pullStrms = ((PullBufferDataSource) source).getStreams();
unfinishedStrms = new SourceStream[pullStrms.length];

// For pull data sources, we'll start a thread per
// stream to pull data from the source.
loops = new Loop[pullStrms.length];
for (int i = 0; i < pullStrms.length; i++) {
loops = new Loop(this, pullStrms);
unfinishedStrms = pullStrms;
}

} else {

// This handler only handles push or pull buffer datasource.
throw new IncompatibleSourceException();

}

this.source = source;
readBuffer = new Buffer();
}

/**
* For completeness, DataSink's require this method.
* But we don't need it.
*/
public void setOutputLocator(MediaLocator ml) {
}

public MediaLocator getOutputLocator() {
return null;
}

public String getContentType() {
return source.getContentType();
}

/**
* Our DataSink does not need to be opened.
*/
public void open() {
}

public void start() {
try {
source.start();
} catch (IOException e) {
System.err.println(e);
}

// Start the processing loop if we are dealing with a
// PullBufferDataSource.
if (loops != null) {
for (int i = 0; i < loops.length; i++)
loops.restart();
}
}

public void stop() {
try {
source.stop();
} catch (IOException e) {
System.err.println(e);
}

// Start the processing loop if we are dealing with a
// PullBufferDataSource.
if (loops != null) {
for (int i = 0; i < loops.length; i++)
loops.pause();
}
}

public void close() {
stop();
if (loops != null) {
for (int i = 0; i < loops.length; i++)
loops.kill();
}
}

public void addDataSinkListener(DataSinkListener dsl) {
if (dsl != null)
if (!listeners.contains(dsl))
listeners.addElement(dsl);
}

public void removeDataSinkListener(DataSinkListener dsl) {
if (dsl != null)
listeners.removeElement(dsl);
}

protected void sendEvent(DataSinkEvent event) {
if (!listeners.isEmpty()) {
synchronized (listeners) {
Enumeration list = listeners.elements();
while (list.hasMoreElements()) {
DataSinkListener listener =
(DataSinkListener) list.nextElement();
listener.dataSinkUpdate(event);
}
}
}
}

/**
* This will get called when there's data pushed from the
* PushBufferDataSource.
*/
public void transferData(PushBufferStream stream) {
try {
stream.read(readBuffer);
} catch (IOException e) {
System.err.println(e);
sendEvent(new DataSinkErrorEvent(this, e.getMessage()));
return;
}

processBuffer(readBuffer);

// Check to see if we are done with all the streams.
if (readBuffer.isEOM() && checkDone(stream)) {
sendEvent(new EndOfStreamEvent(this));
}
}

/**
* This is called from the Loop thread to pull data from
* the PullBufferStream.
*/
public boolean readPullData(PullBufferStream stream) {
try {
stream.read(readBuffer);
} catch (IOException e) {
System.err.println(e);
return true;
}

processBuffer(readBuffer);

if (readBuffer.isEOM()) {
// Check to see if we are done with all the streams.
if (checkDone(stream)) {
System.err.println("All done!");
close();
}
return true;
}
return false;
}

/**
* Check to see if all the streams are processed.
*/
public boolean checkDone(SourceStream strm) {
boolean done = true;

for (int i = 0; i < unfinishedStrms.length; i++) {
if (strm == unfinishedStrms)
unfinishedStrms = null;
else if (unfinishedStrms != null) {
// There's at least one stream that's not done.
done = false;
}
}
return done;
}

void processBuffer(Buffer buffer) {
if (buffer.getFormat() instanceof AudioFormat)
System.err.println("Read audio data:");
else
System.err.println("Read video data:");
System.err.println(" Time stamp: " + buffer.getTimeStamp());
System.err.println(" Sequence #: " + buffer.getSequenceNumber());
System.err.println(" Data length: " + buffer.getLength());

if (buffer.getSequenceNumber() == 1) {
VideoFormat format = (VideoFormat) buffer.getFormat();
Dimension size = format.getSize();
System.out.println("size: " + size);

BufferToImage b2i = new BufferToImage(format);
BufferedImage bi = (BufferedImage) b2i.createImage(buffer);
if (bi != null) {
try {
ImageIO.write(bi, "jpg", new File("c:\\tmp\\test.jpg"));
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
} else {
System.out.println("image == null!");
}
}

if (buffer.isEOM())
System.err.println(" Got EOM!");
}

public Object[] getControls() {
return new Object[0];
}

public Object getControl(String name) {
return null;
}
}

/**
* A thread class to implement a processing loop.
* This loop reads data from a PullBufferDataSource.
*/
class Loop extends Thread {

DataSourceHandler handler;
PullBufferStream stream;
boolean paused = true;
boolean killed = false;

public Loop(DataSourceHandler handler, PullBufferStream stream) {
this.handler = handler;
this.stream = stream;
start();
}

public synchronized void restart() {
paused = false;
notify();
}

/**
* This is the correct way to pause a thread; unlike suspend.
*/
public synchronized void pause() {
paused = true;
}

/**
* This is the correct way to kill a thread; unlike stop.
*/
public synchronized void kill() {
killed = true;
notify();
}

/**
* This is the processing loop to pull data from a
* PullBufferDataSource.
*/
public void run() {
while (!killed) {
try {
while (paused && !killed) {
wait();
}
} catch (InterruptedException e) {
}

if (!killed) {
boolean done = handler.readPullData(stream);
if (done)
pause();
}
}
}
}

/**
* Main program
*/
public static void main(String[] args) throws Exception {

DataSource ds =
Manager.createDataSource(
new File("c:\\tmp\\test.avi").toURL());
DataSourceReader dsr = new DataSourceReader();
if (!dsr.open(ds))
System.exit(0);
}
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top