JMU
Synchronized Auditory and Visual Content
An Introduction with Examples in Java


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Formats
CODECs
Performance Issues
Initiating the "Transfer"
Synchronized AV Content in Java

The Player Class

javaexamples/video/PlayerPanel.java
        import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import javax.media.*;
import javax.swing.*;


/**
 * A JPanel that contains a media player 
 * (using the Java Media Framework)
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 *
 */
public class PlayerPanel extends    JPanel
                         implements ControllerListener
{
    protected boolean    includeVisual, includeControls;
    protected Player     player;


    /**
     * Constructor
     *
     */
    public PlayerPanel(boolean includeVisual, 
                       boolean includeControls)
    {
        player = null;

        this.includeVisual   = includeVisual;
        this.includeControls = includeControls;

        setLayout(new BorderLayout());
    }
    


    /**
     * Load a media file
     *
     * @param fn  The name of the file
     */
    public void loadFile(String fn)
    {
        Component       c;
        URL             url;

        try {

            url = new URL("file:"+
                          System.getProperty("user.dir")+
                          "/"+
                          fn);

            player = Manager.createPlayer(url);
            player.addControllerListener(this);
            player.start();

        } catch (MalformedURLException mue) {

            JOptionPane.showMessageDialog(this, 
                          "Unable to open file!");

        } catch (NoPlayerException npe) {
            
            JOptionPane.showMessageDialog(this,
                          "Unable to create a player!");

        } catch (IOException ioe) {
            
            JOptionPane.showMessageDialog(this,
                          "Unable to connect to source!");
        }

    }



    /**
     * Handle controllerUpdate events 
     * (required by ControllerListener)
     *
     * @param evt   The ControllerEvent
     */
    public synchronized void controllerUpdate(ControllerEvent evt) 
    {
        Component c;

        if (evt instanceof RealizeCompleteEvent) {

            c = player.getVisualComponent();
            if ((c != null) && (includeVisual)) {

                add(c, BorderLayout.CENTER);
            }

            c = player.getControlPanelComponent();
            if ((c != null) && (includeControls)) {

                add(c, BorderLayout.SOUTH);
            }

            // Force a re-layout
            validate();
        }

    }
}


        
javaexamples/video/PlayerPanelDriver.java
        import java.awt.*;
import javax.swing.*;

/**
 * An example that uses a media player
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 *
 */
public class PlayerPanelDriver
{
    /**
     * The entry point of the example
     *
     * @param args  The command line arguments
     */
    public static void main(String[] args)
    {
        boolean          includeVisual;
        JFrame           f;
        Container        contentPane;
        PlayerPanel      p;


        f = new JFrame();

        contentPane = f.getContentPane();
        contentPane.setLayout(new BorderLayout());

        if (args[0].indexOf(".mpg") >= 0) includeVisual = true;
        else                              includeVisual = false;

        p = new PlayerPanel(includeVisual, true);

        contentPane.add(p, BorderLayout.CENTER);

        f.setSize(320,300);
        f.setVisible(true);


        p.loadFile(args[0]);
    }
}
        
Processing Synchronized AV Content in Java
Processor States in Java
Processor States in Java (cont.)

javaexamples/video/ProcessorPanelDriver.java
        import java.awt.*;
import javax.swing.*;

/**
 * An example that uses a media processor
 * to play content
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 *
 */
public class ProcessorPanelDriver
{
    /**
     * The entry point of the example
     *
     * @param args  The command line arguments
     */
    public static void main(String[] args)
    {
        JFrame           f;
        Container        contentPane;
        ProcessorPanel   p;


        f = new JFrame();

        contentPane = f.getContentPane();
        contentPane.setLayout(new BorderLayout());

        p = new ProcessorPanel();

        contentPane.add(p, BorderLayout.CENTER);

        f.setSize(320,300);
        f.setVisible(true);


        p.loadAndStart(args[0]);
    }
}
        
javaexamples/video/ProcessorPanel.java
        import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import javax.media.*;
import javax.swing.*;


/**
 * A JPanel that contains a media processor 
 * (using the Java Media Framework)
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 *
 */
public class ProcessorPanel extends    JPanel
                            implements ControllerListener
{
    protected boolean       transitionOK;
    protected Object        synchronizer;
    protected Processor     processor;


    /**
     * Constructor
     *
     */
    public ProcessorPanel()
    {
        processor = null;

        synchronizer = new Object();
        transitionOK = true;

        setLayout(new BorderLayout());
    }



    /**
     * Get the Processor associated with this ProcessorPanel
     *
     * @return  The Processor
     */
    public Processor getProcessor()
    {
        return processor;
    }


    /**
     * Configure the Processor
     *
     * @return true of the Processor was realized
     */
    public boolean configure()
    {
        boolean status;

        status = false;
            
            // Put the Processor into the configured state
            processor.configure();

            if (waitFor(processor.Configured)) {

                
                // Set the content descriptor to null so that the
                // processor can be used as a player
                processor.setContentDescriptor(null);

                status = true;

            } else {

                JOptionPane.showMessageDialog(this, 
                    "Unable to configure the processor!");
                
            }

        return status;
    }








    /**
     * Handle controllerUpdate events 
     * (required by ControllerListener)
     *
     * @param evt   The ControllerEvent
     */
    public synchronized void controllerUpdate(ControllerEvent evt) 
    {
        if ( (evt instanceof ConfigureCompleteEvent) ||
             (evt instanceof RealizeCompleteEvent) || 
             (evt instanceof PrefetchCompleteEvent) ) {

            // Synchronize this block
            synchronized(synchronizer) {
                
                transitionOK = true;
                synchronizer.notifyAll();
            }

        } else if (evt instanceof ResourceUnavailableEvent) {

            // Synchronize this block
            synchronized(synchronizer) {
                
                transitionOK = false;
                synchronizer.notifyAll();
            }
        }
    }






    /**
     * Load and start a media file
     *
     * @param fn  The name of the file
     */
    public void loadAndStart(String fn)
    {
        boolean     status;

        status = loadFile(fn);
        if (status) status = configure();
        if (status) status = realize();


    }




    /**
     * Load a media file
     *
     * @param fn  The name of the file
     * @return    true if the process completed successfully
     */
    public boolean loadFile(String fn)
    {
        boolean         status;
        URL             url;

        status = false;
        try {
            
            url = new URL("file:"+
                                  System.getProperty("user.dir")+
                                  "/"+fn);

            if (url != null) {

                processor = Manager.createProcessor(url);
                processor.addControllerListener(this);
                status = true;

            }

        } catch (NoProcessorException npe) {
            
            JOptionPane.showMessageDialog(this,
                                 "Unable to create a processor!");
        } catch (IOException ioe) {
            
            JOptionPane.showMessageDialog(this,
                                 "Unable to connect to source!");
        }

        return status;
    }







    /**
     * Realize the Processor
     *
     */
    public boolean realize()
    {
        boolean         status;
        Component       c;


        status = false;

            // Put the Processor into the realized state
            processor.prefetch();
            
            if (waitFor(processor.Prefetched)) {
                
                // The Processor has been realized so the
                // components can be layed out
                c = processor.getVisualComponent();
                if (c != null) add(c, BorderLayout.CENTER);
                c = processor.getControlPanelComponent();
                if (c != null) add(c, BorderLayout.SOUTH);
                validate();
                
                
                // Start the Processor
                processor.start();

                status = true;
                        
            } else {
                        
                JOptionPane.showMessageDialog(this, 
                             "Unable to realize the processor!");
            }

        return status;
    }





    /**
     * Block the thread of execution until the Processor has 
     * transitioned into the given state.
     *
     * @param state   The state to wait for
     * @return        false if the transition failed, true otherwise
     */
    protected boolean waitFor(int state)
    {
        // Make this block synchronized
        synchronized(synchronizer) {

            try {

                while ((processor.getState() != state) && 
                      (transitionOK)) {

                    synchronizer.wait();
                }

            } catch (InterruptedException ie) {

                // Ignore it
            }
        }

        return transitionOK;
    }

}


        
Frame-Based Processing

A Simple Example

javaexamples/video/RGBRenderer.java
        import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.util.*;
import javax.media.*;
import javax.media.renderer.*;
import javax.media.format.*;

/**
 * A Canvas that implements the VideoRenderer interface
 * and, hence, can be used to display video.
 *
 * This particular implementation can only be used with RGB
 * .mov files
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 */
public class RGBRenderer implements VideoRenderer {

    protected boolean              started;
    protected Buffer               lastBuffer;
    protected Component            component;
    protected int                  inputHeight, inputWidth;
    protected Format[]             supportedFormats;
    protected Image                destImage;
    protected MemoryImageSource    sourceImage;
    protected Rectangle            bounds;
    protected RGBFormat            inputFormat, supportedRGB;


    protected static final int rMask = 0x000000FF;
    protected static final int gMask = 0x0000FF00;
    protected static final int bMask = 0x00FF0000;


    /**
     * Constructor
     */
    public RGBRenderer() 
    {
        lastBuffer = null;
        component  = null;
        bounds     = null;
        started    = false;

        inputWidth  = 0;
        inputHeight = 0;

        // Prepare supported input formats and preferred format
        supportedRGB = new RGBFormat(null,      // size
                          Format.NOT_SPECIFIED, // maxDataLength
                          int[].class,          // buffer type
                          Format.NOT_SPECIFIED, // frame rate
                          32,                   // bitsPerPixel
                          rMask, gMask, bMask,  // component masks
                          1,                    // pixel stride
                          Format.NOT_SPECIFIED, // line stride
                          Format.FALSE,         // flipped
                          Format.NOT_SPECIFIED  // endian
                          );

        supportedFormats = new VideoFormat[1];
        supportedFormats[0] = supportedRGB;
    }



    /**
     * Close the Renderer (required by VideoRenderer)
     */
    public void close() 
    {
        // Do nothing
    }



    /**
     * Get the portion of the Component being used by 
     * this Renderer (required by VideoRenderer)
     *
     * @return  The Rectangle used (null for the whole Component)
     */
    public Rectangle getBounds() 
    {
        return bounds;
    }




    /**
     * Get the Component that this Renderer is 
     * using (required by VideoRenderer)
     *
     * @return  The Component
     */
    public Component getComponent() 
    {
        if (component == null) {

            component = new RendererCanvas(this);
        }

        return component;
    }





    /**
     * Get the Controls for this renderer 
     * (required by VideoRenderer)
     *
     * Since this implementation does not support Controls it
     * returns an empty array.
     *
     * @return   The controls
     */
    public Object[] getControls() 
    {
        return (Object[]) new Control[0];
    }



    /**
     * Get a Control based on a control type 
     * (required by VideoRenderer)
     *
     * Since this implementation does not support Controls it
     * returns an empty array.
     *
     * @return   The controls
     */
    public Object getControl(String controlType) 
    {
        return null;
    }



    /**
     * Get the name of this Renderer 
     * (required by VideoRenderer)
     *
     * @return The name
     */
    public String getName() {

        return "RendererCanvas";
    }



    /**
     * Get the size of the input data
     *
     * @return The size (in pixels)
     */
    public Dimension getSize()
    {
        return new Dimension(inputWidth, inputHeight);
    }



    /**
     * Get the array of supported formats 
     * (required by VideoRenderer)
     *
     * @return  The array of supported formats
     */
    public Format[] getSupportedInputFormats() 
    {
        return supportedFormats;
    }



    /**
     * Open this Renderer (required by VideoRenderer)
     *
     */
    public void open() throws ResourceUnavailableException 
    {
        sourceImage = null;
        destImage   = null;
        lastBuffer  = null;
    }



    /**
     * Paint the current image
     *
     * @param g   The Graphics context
     */
    public void paintImage(Graphics g)
    {
        Rectangle tempBounds;


        if ( (g == null) || (destImage == null) ||
             (component == null)) return;

        if (bounds == null) {

            tempBounds = component.getBounds();
            tempBounds.x = 0;
            tempBounds.y = 0;
            
        } else {

            tempBounds = bounds;
        }

        g.drawImage(destImage, tempBounds.x, tempBounds.y,
                    tempBounds.width, tempBounds.height,
                    0, 0, inputWidth, inputHeight, component);
    }





    /**
     * Process the input data and render it
     *
     * @param buffer  The input data
     * @return        BUFFER_PROCESSED_OK if successful
     */
    public synchronized int process(Buffer buffer) 
    {
        Object    data;
        Format    inf;
        Graphics  g;

        // Initial error checking
        if (component == null) return BUFFER_PROCESSED_FAILED;

        inf = buffer.getFormat();
        if (inf == null)       return BUFFER_PROCESSED_FAILED;

        if ( (inf != inputFormat) || 
             (!buffer.getFormat().equals(inputFormat)) ) {

            if (setInputFormat(inf)!=null) {
                return BUFFER_PROCESSED_FAILED;
            }
        }

        data = buffer.getData();
        if (!(data instanceof int[])) {
               return BUFFER_PROCESSED_FAILED;
        }



        // Check to see if the data have changed
        if (lastBuffer != buffer) {

            lastBuffer = buffer;
            buildImage(buffer);
        }


        // Render the image
        sourceImage.newPixels(0, 0, inputWidth, inputHeight);
        g = component.getGraphics();

        if (g != null) paintImage(g);

        return BUFFER_PROCESSED_OK;
    }





    /** 
     * Reset the Renderer (required by VideoRenderer)
     *
     */
    public void reset() 
    {
        // Do nothing
    }



    /**
     * Set the portion of the Component to use 
     * (required by VideoRenderer)
     *
     * @param rect  The area to use (null for the whole Component)
     */
    public void setBounds(Rectangle bounds) 
    {
        this.bounds = bounds;
    }




    /**
     * Set the Component that the Renderer should 
     * use (required by VideoRenderer)
     *
     * @return false if the Renderer can't use the Component
     */
    public boolean setComponent(Component comp) 
    {

        component = comp;
        return true;
    }



    /**
     * Set the input format (required by VideoRenderer)
     *
     * @param format  The Format of the input data
     * @return        The Format that was set or null
     */
    public Format setInputFormat(Format format) 
    {
        Dimension  size;
        Format     retval;

        if ( (format != null) && 
             (format instanceof RGBFormat) &&
             (format.matches(supportedRGB))    ) {

            inputFormat = (RGBFormat)format;

            size = inputFormat.getSize();
            inputWidth  = size.width;
            inputHeight = size.height;

            retval = format;

        } else {

            retval = null;
        }

        return retval;
    }





    /**
     * Start the Renderer (required by VideoRenderer)
     */
    public void start() 
    {
        started = true;
    }



    /**
     * Stop the Renderer (required by VideoRenderer)
     */
    public void stop() 
    {
        started = false;
    }




    /**
     * Build a new image from the input data
     *
     * @param buffer  The input data
     */
    protected void buildImage(Buffer buffer) 
    {
        DirectColorModel  colorModel;
        Object            data;
        RGBFormat         format;


        data = buffer.getData();

        if (!(data instanceof int[])) return;

        format = (RGBFormat) buffer.getFormat();

        colorModel=new DirectColorModel(format.getBitsPerPixel(),
                                        format.getRedMask(),
                                        format.getGreenMask(),
                                        format.getBlueMask());

        sourceImage=new MemoryImageSource(format.getLineStride(),
                                          format.getSize().height,
                                          colorModel,
                                          (int[])data, 0,
                                          format.getLineStride());
        sourceImage.setAnimated(true);

        sourceImage.setFullBufferUpdates(true);

        if (component != null) {

            destImage = component.createImage(sourceImage);
            component.prepareImage(destImage, component);
        }
    }
}
    
    
        
javaexamples/video/RendererCanvas.java
        import java.awt.*;

/**
 * A Canvas that is used by an RGBRenderer to display video.
 *
 * This particular implementation can only be used with an
 * RGBRenderer.
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 */
public class RendererCanvas extends Canvas
{

    RGBRenderer       renderer;


    /**
     * Constructor
     *
     * @param renderer The RGBRenderer that determines the size
     */
    public RendererCanvas(RGBRenderer renderer)
    {
        this.renderer = renderer;
    }



    /**
     * Get the minimum size of this Canvas
     */
    public Dimension getMinimumSize() 
    {
        return renderer.getSize();
    }




    /**
     * Get the preferred size of this Canvas
     */
    public Dimension getPreferredSize() 
    {
        return getMinimumSize();
    }


    /**
     * Update this Component
     *
     * @param g   The Graphics context
     */
    public void update(Graphics g) 
    {
        // Do nothing
    }


    /**
     * Paint this Component
     *
     * @param g   The Graphics context
     */
    public void paint(Graphics g) 
    {
        // This method needs to handle repaints when the
        // movie is paused
        renderer.paintImage(g);
    }
}
        
javaexamples/video/RendererDriver.java
        import java.awt.*;
import javax.media.*;
import javax.media.control.*;
import javax.media.format.*;
import javax.swing.*;


/**
 * An example that uses a custom VideoRenderer
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 *
 */
public class RendererDriver
{
    /**
     * The entry point of the example
     *
     * @param args  The command line arguments
     */
    public static void main(String[] args)
    {
        boolean          status;
        JFrame           f;
        Container        contentPane;
        int              i;
        ProcessorPanel   p;
        RGBRenderer      renderer;
        TrackControl     videoControl;
        TrackControl[]   trackControls;


        f = new JFrame();

        contentPane = f.getContentPane();
        contentPane.setLayout(new BorderLayout());

        p = new ProcessorPanel();

        contentPane.add(p, BorderLayout.CENTER);

        status = false;
        status = p.loadFile(args[0]);

        status = p.configure();

        if (status) {

            status = false;

            // Get the video track control (if there is one)
            trackControls = p.getProcessor().getTrackControls();

            videoControl = null;
            for (i=0; i < trackControls.length; i++) {

                if (trackControls[i].getFormat() 
                           instanceof VideoFormat) {
                    
                    videoControl = trackControls[i];
                    break;
                }
            }

            if (videoControl != null) {

                // Set the Renderer for the video track control
                try {
                    
                    renderer = new RGBRenderer();
                    videoControl.setRenderer(renderer);
                    status = true;
                    
                } catch (UnsupportedPlugInException upie) {
                    
                    System.out.println("Unsupported PlugIn");
                }
            }
        }
         

        if (status) {

            p.realize();
        }



        f.setSize(320,300);
        f.setVisible(true);


    }
}
        
Frame-Based Processing

A More Complicated Example

javaexamples/video/PopUpRenderer.java
        import java.awt.*;
import javax.media.*;


/**
 * An RGBRenderer that "pops-up" text
 *
 * This particular implementation can only be used with RGB
 * .mov files
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 */
public class PopUpRenderer extends RGBRenderer 
{
    protected int       index, frame;
    protected int[]     duration, frames;
    protected String[]  text;

    /**
     * Constructor
     *
     * @param frames     Array of frame numbers for popups
     * @param text       Array of text for popups
     * @param duration   Array of durations (in frames) for popups
     */
    public PopUpRenderer(int[] frames, 
                         String[] text, 
                         int[] duration)
    {
        this.frames   = frames;
        this.text     = text;
        this.duration = duration;

        frame = -1;
        index =  0;
    }
    


    /**
     * Paint the current image
     *
     * @param g   The Graphics context
     */
    public void paintImage(Graphics g)
    {
        // This should be buffered to avoid flicker

        super.paintImage(g);

        if (index < frames.length) {
                
            if ((frame >= frames[index]) && 
                (frame <= frames[index]+duration[index])) {
                
                
                g.setColor(Color.white);
                g.fillRect(50,50,150,25);
                g.setColor(Color.black);
                g.drawString(text[index], 70, 70);
                
                if (frame == 
                    frames[index]+duration[index]) index++;
            }
        }
    }



    

    /**
     * Process the input data and render it
     *
     * @param buffer  The input data
     * @return        BUFFER_PROCESSED_OK if successful
     */
    public synchronized int process(Buffer buffer) 
    {
        frame++;

        return super.process(buffer);
    }


}
        
javaexamples/video/PopUpRendererDriver.java
        import java.awt.*;
import javax.media.*;
import javax.media.control.*;
import javax.media.format.*;
import javax.swing.*;


/**
 * An example that uses a custom VideoRenderer that
 * includes "pop-up" text
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 *
 */
public class PopUpRendererDriver
{
    /**
     * The entry point of the example
     *
     * @param args  The command line arguments
     */
    public static void main(String[] args)
    {
        boolean          status;
        JFrame           f;
        Container        contentPane;
        int              i;
        int[]            frames, duration;
        ProcessorPanel   p;
        PopUpRenderer    renderer;
        String[]         text;
        TrackControl     videoControl;
        TrackControl[]   trackControls;


        text = new String[2];
        text[0] = "Nice Mask!";
        text[1] = "Did he say organisms?";

        frames = new int[2];
        frames[0] = 70;
        frames[1] = 135;

        duration = new int[2];
        duration[0] = 10;
        duration[1] = 20;

        f = new JFrame();

        contentPane = f.getContentPane();
        contentPane.setLayout(new BorderLayout());

        p = new ProcessorPanel();

        contentPane.add(p, BorderLayout.CENTER);

        status = false;
        status = p.loadFile(args[0]);

        status = p.configure();

        if (status) {

            status = false;

            // Get the video track control (if there is one)
            trackControls = p.getProcessor().getTrackControls();

            videoControl = null;
            for (i=0; i < trackControls.length; i++) {

                if (trackControls[i].getFormat() 
                          instanceof VideoFormat) {
                    
                    videoControl = trackControls[i];
                    break;
                }
            }

            if (videoControl != null) {

                // Set the Renderer for the video track control
                try {
                    
                    renderer = 
                        new PopUpRenderer(frames, text, duration);

                    videoControl.setRenderer(renderer);
                    status = true;
                    
                } catch (UnsupportedPlugInException upie) {
                    
                    System.out.println("Unsupported PlugIn");
                }
            }
        }
         

        if (status) {

            p.realize();
        }



        f.setSize(320,300);
        f.setVisible(true);


    }
}