Processing Media

   

JMF processor programs manipulate media. After manipulation, the results can be played back to a user , or delivered to some other destination (such as a file). These programs typically present controls to perform various processing operations.

Processing involves working with demultiplexers, multiplexers, codecs, effect filters, and renderers. As previously mentioned, demultiplexers extract individual tracks from a media stream, whereas multiplexers combine individual tracks into a single media stream. Codecs (compressors/decompressors) perform compression and decompression operations on individual tracks. Effect filters modify track data to achieve special effects. Preprocess effect filtering occurs before a codec processes a track. In contrast, postprocess effect filtering occurs after a codec has processed a track. (Normally, preprocess effect filtering is used.) Finally, renderers are output devices (such as speakers and monitors ). Some renderers can composite (overlay) one track of data over another track. For example, a renderer could overlay a video track with a track that consists of text.

Processing media streams requires processors ”objects created from classes that implement the Processor interface. You can obtain a processor by calling one of Manager 's createProcessor methods . (Because the Processor interface extends Player , a processor is nothing more than a player with processing capability.)

A processor adds two additional transition states to the six player states: Configuring and Configured. While in the Configuring state , a processor gathers the information it needs to construct TrackControl objects for each track. A TrackControl object is created from a class that implements the TrackControl interface. A processor provides TrackControl to query, control, and manipulate a track's data.

After it's finished configuring, the processor moves into the Configured state and posts a ConfigureCompleteEvent object. After this state has been reached, you can set the processor's output content type (by calling its setContentDescriptor method) and TrackControl options. When you're finished specifying these options, you can call realize to move the processor into the Realizing state and begin the realization process.

Figure 20.6 illustrates the various states that a processor can be in at any point in its duration.

Figure 20.6. Unlike players, a processor transitions through eight states as it operates. The two new states are Configuring and Configured.

graphics/20fig06.gif

Suppose you wanted to create a processor that would play a movie from a negative perspective. In other words, you want to see each frame's negative. Listing 20.5 presents source code to a ProcessorDemo application that creates a processor with a negative effect filter for achieving this task.

Listing 20.5 The ProcessorDemo Application Source Code
 // ProcessorDemo.java import javax.media.*; import javax.media.control.*; import java.awt.*; import java.awt.event.*; class ProcessorDemo extends Frame implements ControllerListener {    Component cc, vc;    TrackControl [] tc;    Processor processor;    ProcessorDemo (String title, String mediaURL)    {       super (title);       addWindowListener (new WindowAdapter ()                          {                              public void windowClosing (WindowEvent e)                              {                                 processor.close ();                                 System.exit (0);                              }                          });       try       {          MediaLocator ml = new MediaLocator (mediaURL);          processor = Manager.createProcessor (ml);       }       catch (java.io.IOException e)       {          terminate (e.toString ());       }       catch (NoProcessorException e)       {          terminate ("Could not find a processor.");       }       if (processor == null)           terminate ("Trouble creating processor.");       processor.addControllerListener (this);       processor.configure ();       while (processor.getState () != Processor.Configured);       processor.setContentDescriptor (null);       TrackControl [] tc = processor.getTrackControls ();       if (tc.length > 0)       {           Codec [] c = new Codec [1];           c [0] = new NegativeEffect ();           try           {              tc [0].setCodecChain (c);           }           catch (UnsupportedPlugInException e2)           {              terminate (e2.toString ());           }       }       processor.realize ();    }    public void controllerUpdate (ControllerEvent e)    {       if (e instanceof ControllerClosedEvent)           System.exit (0);       if (e instanceof EndOfMediaEvent)       {           processor.setMediaTime (new Time (0));           return;       }       if (e instanceof RealizeCompleteEvent)       {           vc = processor.getVisualComponent ();           if (vc != null)               add (vc, BorderLayout.CENTER);           cc = processor.getControlPanelComponent ();           if (cc != null)               add (cc, (vc != null) ? BorderLayout.SOUTH                                     : BorderLayout.CENTER);           pack ();           setResizable (false);           setVisible (true);       }    }    void terminate (String s)    {       System.out.println (s);       System.exit (-1);    }    public static void main (String [] args)    {       if (args.length != 1)       {           System.out.println ("usage: java ProcessorDemo url");           return;       }       new ProcessorDemo ("Processor Demo", args [0]);    } } 

ProcessorDemo calls configure to transition to the Configured state. However, it might take some time before this state is reached. Therefore, ProcessorDemo enters a busy-wait by repeatedly calling its getState method to interrogate the current state. After the Configured state has been reached, it exits the busy-wait.

By passing null to its setContentDescriptor method, the processor indicates that it will function as a player ”a user will be able to view processed output. If track controls are present, a negative effect object (created from the NegativeEffect class) will be added to a chain of codecs for the first track. (The assumption is that the first track is a video track.) This is accomplished by calling TrackControl 's setCodecChain method. (If you examine JMF's documentation, you'll discover that an effect filter is a special kind of codec.) The processor then calls its realize method to enter the Realizing state. The resulting output is shown in Figure 20.7 .

Figure 20.7. ProcessorDemo 's output shows a negative of a frame taken from the OrionMosaic.mpg movie.

graphics/20fig07.gif

To achieve a negative effect, ProcessorDemo requires a separate class called NegativeEffect . The source code for this effect filter is presented in Listing 20.6 .

Listing 20.6 The NegativeEffect Effect Filter Source Code
 // NegativeEffect.java import javax.media.*; import javax.media.format.*; public class NegativeEffect implements Effect {    /** effect name **/    private static String EffectName = "NegativeEffect";   /** chosen input format **/    protected RGBFormat inputFormat;   /** chosen output format **/    protected RGBFormat outputFormat;   /** supported input formats **/    protected Format [] supportedInputFormats;    /** supported output formats **/    protected Format [] supportedOutputFormats;   /** initialize the formats **/    public NegativeEffect ()    {       supportedInputFormats = new Format []       {          new RGBFormat ()       } ;       supportedOutputFormats = new Format []       {          new RGBFormat ()       } ;    }    /** get the resources needed by this effect **/    public void open () throws ResourceUnavailableException    {       System.out.println ("open");    }    /** free the resources allocated by this effect **/    public void close ()    {       System.out.println ("close");    }    /** reset the effect **/    public void reset ()    {       System.out.println ("reset");    }    /** no controls for this simple effect **/    public Object [] getControls ()    {       System.out.println ("getControls");       return new Controls [0];    }   /** return the control based on a control type for the effect **/    public Object getControl (String controlType)    {       System.out.println ("getControl [controlType = "                           + controlType + "]");       try       {          Class cls = Class.forName (controlType);          Object cs [] = getControls ();          for (int i = 0; i < cs.length; i++)          {               if (cls.isInstance (cs [i]))                   return cs [i];          }          return null;       }       catch (Exception e)       {          // no such controlType or such control          return null;       }    }    /************** format methods *************/    /** set the input format **/    public Format setInputFormat (Format input)    {       System.out.println ("setInputFormat [input = " + input + "]");       // the following code assumes valid format       inputFormat = (RGBFormat) input;       return (Format) inputFormat;    }    /** set the output format **/    public Format setOutputFormat (Format output)    {       System.out.println ("setOutputFormat [output = " + output + "]");       // the following code assumes valid format       outputFormat = (RGBFormat) output;       return (Format) outputFormat;    }    /** get the input format **/    protected Format getInputFormat ()    {       System.out.println ("getInputFormat");       return inputFormat;    }    /** get the output format **/    protected Format getOutputFormat ()    {       System.out.println ("getOutputFormat");       return outputFormat;    }    /** supported input formats **/    public Format [] getSupportedInputFormats ()    {       System.out.println ("getSupportedInputFormats");       return supportedInputFormats;    }   /** output Formats for the selected input format **/    public Format [] getSupportedOutputFormats (Format in)    {       System.out.println ("getSupportedOutputFormats [in = " + in                           + "]");       if (in == null)           return supportedOutputFormats;       if (!(in instanceof RGBFormat))           return new Format [0];      RGBFormat irf = (RGBFormat) in;      RGBFormat orf = (RGBFormat) in.clone ();      return new Format [] {  orf } ;    }    /** return effect name **/    public String getName ()    {       System.out.println ("getName");       return EffectName;    }    /** do the processing **/    public int process (Buffer inBuffer, Buffer outBuffer)    {       // == prolog       Object o1 = inBuffer.getData ();       int inLength = inBuffer.getLength ();       int inOffset = inBuffer.getOffset ();       if (!(o1 instanceof short []) && ! (o1 instanceof int []))           return BUFFER_PROCESSED_FAILED;       Object o2 = outBuffer.getData ();       if (o2 != null)       {           if (!(o2 instanceof short []) && !(o2 instanceof int []))               return BUFFER_PROCESSED_FAILED;       }       else       {           if (o1 instanceof short [])               outBuffer.setData (new short [inLength]);           else               outBuffer.setData (new int [inLength]);           o2 = outBuffer.getData ();       }       int outOffset = outBuffer.getOffset ();       // == main       if (o1 instanceof short [])       {           short [] inData = (short []) o1;           short [] outData = (short []) o2;           for (int i = 0; i < inLength; i++)                outData [outOffset++] = (short) ~inData [inOffset++];       }       else       {           int [] inData = (int []) o1;           int [] outData = (int []) o2;           for (int i = 0; i < inLength; i++)                outData [outOffset++] = ~inData [inOffset++];                     }       outBuffer.setFormat (outputFormat);       outBuffer.setLength (inLength);       outBuffer.setOffset (0);       return BUFFER_PROCESSED_OK;    } } 

NegativeEffect implements the Effect interface (directly), along with the Codec , PlugIn , and Controls interfaces (indirectly). As a result, it must supply a variety of methods. To learn more about these methods, check out Chapter 6, "Extending JMF," in the Java Media Framework API Guide . This chapter discusses these methods in the context of building an effect filter. (The Java Media Framework API Guide , and the rest of the JMF documentation, is part of the JMF download.)

Troubleshooting Tip

After you become comfortable with JMF, you'll probably want to explore some advanced topics, including how to seek to a specific frame in a movie, wrap a movie around a 3D image, and generate a movie from a series of JPEG images. If you're not sure how to make this happen, check out "JMF Solutions" in the "Troubleshooting" section at the end of this chapter.


   


Special Edition Using Java 2 Standard Edition
Special Edition Using Java 2, Standard Edition (Special Edition Using...)
ISBN: 0789724685
EAN: 2147483647
Year: 1999
Pages: 353

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net