Managing the Complexity

As graphically illustrated in the previous chapter, time-based media is a broad category encompassing not only different types of media (for example, audio and video), but also different content types (for example, QuickTime and AVI), and different formats for compression (for example, MPEG and Cinepak). This leads to a plethora of diverse media that differ at the conceptual level (visual or aural) down to the bit sequence by which they are encoded. Further complexity is added by the multitude of hardware devices from which media can be captured, and to which it can be rendered.

On the other hand, the goal of the JMF is to present a uniform, platform independent interface to controlling, processing, capturing, and rendering media. That means, for example, a single program to play media regardless of particulars of its encoding; not a different program for each type (category x content_type x encoding_scheme) of media.

The JMF successfully resolves these two conflicting items by providing four manager classes who's prime role is to track the numerous classes required to support media handling, while shielding the user from that complexity through the provision of a simple and consistent interface. Effectively, the managers act as brokers or intermediaries between user code and the necessarily complex functionality provided by the JMF. This provides user code with a simple, abstracted model; freeing it of unwanted complexity. Figure 8.12 illustrates that conceptual role of the managers. The BBPApplet (Bare Bones Player Applet) from Chapter 7, “Time-based Media and the JMF: An Introduction,” provides a good example of this abstraction afforded by the managers. The applet simply requests that the manager create an object (Player) capable of playing the media it has indicated. The manager responds with an object suitable for the task. The actual object provided will depend on the content type and format of the media in question. However, from the applet's perspective there is simply a Player object which will do the task. The manager hid all the details of finding and constructing an instance of the appropriate class.

Figure 8.12. Role of manager classes (for example, Manager) as registry and shield for user code from the necessary complexity of classes in order to support the multitude of media formats.


The JMF has four manager classes— each with the word Manager in their name, appropriately enough. Each of these classes exposes a number of static methods through which they provide their service. The four classes are as follows:

Manager— The central management class from which Players, Processors, DataSources, and DataSinks are obtained. This manager is discussed in the next subsection.

CaptureDeviceManager— Manager encapsulating knowledge of the capture devices (for example, sound or video capture cards) attached to the machine. This manager is discussed toward the end of the chapter.

PackageManager— A manager providing knowledge of and control over the packages that contribute to JMF's functionality. This manager is discussed in Chapter 9, “RTP and Advanced Time-Based Media Topics.”

PlugInManager— A manager encapsulating knowledge of installed plug-ins, as well as a means of registering new ones. The JMF model of a plug-in incorporates multiplexers, demultiplexers, codecs, effects, and renderers. This manager is discussed in Chapter 9.

Although there are empty constructors for all except the Manager class, all methods exposed by the managers are static: They are invoked using the class name, and don't need an instance of the class constructed before they can be invoked.

Hence, for instance, to obtain information about a particular named CaptureDevice, the code should be written as follows:

String    deviceName = "...";    // Set to the actual name of the device
CaptureDeviceInfo captureDInfo = CaptureDeviceManager.getDevice(deviceName);

rather than

String deviceName="...";    // Set to the actual name of the device
CaptureDeviceManager captureDManager = new CaptureDeviceManager();
CaptureDeviceInfo captureDInfo = captureDManager.getDevice(deviceName);

The Manager Class

The Manager class is the single most important manager class, and arguably the most important class in the JMF given its role in creation of Players, Processors, DataSinks, and DataSources.

Figure 8.13 shows the methods of the Manager class. As can be seen, Manager possesses methods for the creation of DataSources, DataSinks, Players, and Processors. It has already been noted in the previous section that these four classes play the primary roles in the control and processing of media. Given their significance, the importance of Manager as the sole agent of their creation shouldn't be underestimated.

Figure 8.13. The Manager class.


Although subsequent sections discuss the Player, Processor, DataSource, and DataSink classes, their creation through the Manager class is discussed here.

Creating a Player

Objects implementing the Player interface are used in controlling the playback of media. The Manager class provides six methods for the creation of a Player: Three create an Unrealized Player: createPlayer(), whereas the other three create a Realized (that is, already partially resourced and more along the path to being ready to start) Player: createRealizedPlayer().

The three createRealizedPlayer() versions are provided as a means to accelerate the creation of a Player. The method calls blocks (that is, the next line of code doesn't execute until the Player is Realized). This has the advantage that the Realized event doesn't need to be listened for by the invoker of the method (and hence methods such as getVisualComponent() can be called as the next line).

The three versions of Player creation methods (whether for an Unrealized or Realized Player) accept either a MediaLocator, URL, or DataSource as the single parameter. The steps in creating a Player that the Manager follows are as follows:

1.
Convert the URL to a MediaLocator (if URL based method is used).

2.
Create a DataSource for the MediaLocator (if DataSource based method isn't used).

3.
Obtain the Player that can handle the DataSource.

4.
Attach the DataSource to the Player.

5.
Return the Player object.

The URL and MediaLocator based creation methods are the more commonly used because they correspond to the typical scenario in which a Player is employed: The media is in some location (for example, a file) and needs only to be played. On the other hand, the DataSource based method is useful when playing is the end result of a chain that involved other processing (that produced a DataSource) as earlier steps.

Thus the typical usage of Manager to create a Player is as follows:

MediaLocator locator = new MediaLocator(...);    // Specify media location;
Player player = Manager.createPlayer(locator);

Alternatively, a Realized Player could be created with the createRealizedPlayer() method as follows:

MediaLocator = new MediaLocator(...);    // Specify the media location;
Player player = Manager.createRealizedPlayer(locator);

In creating a Player object, Manager follows a set algorithm of searching through the various Player classes looking for one capable of handling the content type that the DataSource (constructed as an earlier part of the process if the DataSource wasn't supplied to the method) specifies. The process is simple linear iteration through the list of constructed classnames until one is found capable of handling the media.

If Manager cannot create a Player, it might throw an IOException (that is, file doesn't exist), a NoPlayerException (that is, a content-type that the JMF does not handle), or in the case of the createRealizedPlayer() methods, a CannotRealizeException (that is, couldn't obtain the resources necessary).

Players will be discussed further in a subsequent section.

Creating a Processor

Objects implementing the Processor interface are used in controlling the processing of media. The Manager class provides four methods for the creation of a Processor: Three create an Unrealized Processor: createProcessor() , whereas the final method creates a Realized (that is, already partially resourced and more along the path to being ready to start) Processor: createRealizedProcessor().

The createRealizedProcessor() method is provided as a means of accelerating the creation of a Processor. The method call blocks (that is, the next line of code doesn't execute until the Processor is Realized). This has the advantage that the Realized event doesn't need to be listened for by the invoker of the method. The createRealizedProcessor() method accepts a single parameter that is a ProcessorModel which fully specifies the input or output format of the media; thus the processing to be performed.

The three variants of createProcessor() have the same form as for Player creation: accepting either a MediaLocator, URL, or DataSource as the single parameter. Similarly, the Manager follows the same process in creation of a Processor as it does in the creation of a Player:

1.
Convert the URL to a MediaLocator (if URL based method is used).

2.
Create a DataSource for the MediaLocator (if DataSource based method isn't used).

3.
Obtain the Processor that can handle the DataSource.

4.
Attach the DataSource to the Processor.

5.
Return the Processor object.

The URL and MediaLocator based creation methods should be used in the case where the processing is the first step in the chain of control. Alternatively, if it is a subsequent step (for example, chained processing) the DataSource based method should be used.

Thus the typical usage of Manager to create a Processor is as follows:

MediaLocator locator = new MediaLocator(...);    // Specify media location;
Processor processor = Manager.createProcessor(locator);

Alternatively, a Realized Processor could be created with the createRealizedProcessor() method as

//Specify the model for processing
ProcessorModel model = new ProcessorModel(...);
// Create the processor
Processor processor = Manager.createRealizedProcessor(model);

In creating a Processor object, Manager follows a set algorithm of searching through the various Processor classes that correspond to the same approach as that used for Player creation.

If Manager cannot create a Processor, it might throw an IOException (that is, file doesn't exist), a NoProcessorException (that is, a content-type that the JMF doesn't handle), or in the case of the createRealizedProcessor() methods, a CannotRealizeException (that is, couldn't obtain the resources necessary).

Processors will be discussed further in a subsequent section.

Creating a DataSource

DataSources are the means by which Players, Processors, or DataSinks obtain their data. Creation of a Player, Processor, or DataSink always involves a DataSource; whether provided explicitly to the creation method (as in the creation of a DataSink), or created as part of the larger process (as in the creation of a Player where a MediaLocator is provided). Details of DataSources are fully discussed in a subsequent section.

There are two methods for creating a DataSource from a location specification (URL or MediaLocator) using createDataSource(). There are also two methods for creating specialized DataSources: merging DataSource (one that combines two or more DataSources) with createMergingDataSource() and creating a cloneable DataSource (one that can be cloned to be processed or played by different systems simultaneously) with createCloneableDataSource().

Listing 8.2 shows a number of the DataSource creation methods being used in a hypothetical scenario in which two DataSources are created, one is cloned (so it can be dual processed), and then the two sources are combined.

Listing 8.2 Hypothetical DataSources Scenario
MediaLocator firstLocation = new MediaLocator(...);    //Location of 1st media
MediaLocator secondLocation = new MediaLocator(...);    // Location of 2nd media
try {
  // Create the two datasources.
  DataSource firstSrc = Manager.createDataSource(firstLocation);
  DataSource secondSrc = Manager.createDataSource(secondLocation);
  // Create cloneable version of 2nd src, then clone it.
  DataSource cloneableSrc = Manager.createCloneableDataSource(secondSrc);
  DataSource cloneA = cloneableSrc.createClone();
  // Create a merged DataSource combining the 1st and 2nd  DataSources.
  DataSource srcArray = new DataSource[2];
  srcArray[0] = firstSrc;
  srcArray[1] = cloneA;
  DataSourced mergedSrcs = Manager.createdMergingDataSource(srcArray);
}

DataSources are identified by the protocol they support. In creation of a DataSource, the Manager class follows a similar approach as that employed for Player and Processor creation. A list of classes supporting the protocol specified (by the URL or MediaLocator) is compiled and that list is linearly searched until a class capable of sourcing the media is found.

Failure to create a DataSource from a URL or MediaLocator will result in the method throwing an IOException or NoDataSourceException. Failure to create a merging DataSource will result in an IncompatibleSourceException being thrown.

Creating a DataSink

DataSinks are used to take the media from a DataSource and render it to a particular location (for instance, a file). The Manager class provides a single method, createDataSink(), for the creation of a DataSink. The method accepts two parameters—a DataSource from which the media is sourced, and a MediaLocator that specifies the destination location.

The steps that the Manager class follows when creating a DataSource instance are similar to those used in the creation of a DataSource—with the protocol of the MediaLocator used to compile a list of DataSink classes that support the protocol. That list is then searched in order to find an appropriate class for which an instance can be created.

Failure to create a DataSink will result in a NoDataSink exception being thrown.

DataSinks are discussed in a later section of this chapter. However as an example of their usage, Listing 8.3 shows a portion (the object creation) of the process in which a DataSink could be used to create a copy of a media file. (Obviously it would be far more efficient to simply copy the file using the operating system commands.)

Listing 8.3 Some of the Major Steps in Using a DataSink Object
String    origin = "file:...";
String    destination = "file:...";
MediaLocator    originLocation = new MediaLocator(origin);
MediaLocator    destinationLocation = new MediaLocator(destination);
Processor     p = Manager.createRealizedProcessor(null);
DataSource    src = p.getDataOutput();
DataSink    dest = Manager.createDataSink(src,destinationLocation);
:        :

Querying the Manager

Besides the methods for creating Players, Processors, DataSources, and DataSinks, the Manager class provides a number of information methods—methods that provide information about the configuration and support of the installed version of the JMF.

These information methods are the various get*() methods:

getHint()— Obtains information about hints provided to JMF.

getCacheDirectory()— Determines what directory the JMF uses for temporary storage.

getDataSourceList()— Determines what DataSource classes support a particular protocol.

getHandlerClassList()— Determines what Player classes support a particular content type.

getProcessorClassList()— Determines what Processor classes support a particular content type.

The getHint() method should be passed one of the constants defined in the Manager class (for example, CACHING) and returns the setting for that as an instance of Object.

The getCacheDirectory() method has no parameters and returns a String.

The three get*List() methods getDataSourceList(), getHandlerClassList(), and getProcessorClassList() each accept a String specifying the content type or protocol for which support is being queried. The methods return a Vector. The elements of the Vector are Strings, and those Strings are the fully qualified names of the classes that provide that support.

Listing 8.4 is an application ManagerQuery, which employs the information gathering methods of Manager in order to provide the user with details about the JMF. In running the program, users can query JMF as to its support (Players, Processors, or DataSources) for different formats and protocols. Alternatively, a complete picture can be produced by not specifying any particular formats or protocols: In this case all classes supporting all known formats are listed. The application can be found on the book's companion Web site. Similarly, a graphical application GUIManagerQuery, based on ManagerQuery, is available from the book's companion Web site. It provides the same functionality as ManagerQuery but with a graphical user interface.

Listing 8.4 The ManagerQuery Application Used to Discover Details About JMF
/*******************************************************************
* ManagerQuery - Query the manager class about the configuration and
* support of the installed JMF version. ManagerQuery is a text-based
* application that provides a report on the support of the JMF for
* Players, Processors and DataSinks.
*
* Without any command-line arguments ManagerQuery prints a complete
* (LONG) list of Player, Processor, and DataSource classes that
* support the various formats, protocols, and content types.
*
* Alternatively it is possible to provide command-line arguments
* specifying the format or protocol for which support is to be
* checked. The means of calling is as follows:
*   java ManagerQuery [ [-h|-p|-d] support1 support2 ... supportN]
* The -h flag specifies handlers (Players) only.
* The -p flag specifies Processors only.
* The -d flag specifies DataSources only.
* Leaving off the flag defaults behaviour to checking for Players
* only.
*
* For instance:
*   java ManagerQuery -h mp3 ulaw
* would list the classes capable of Playing the MP3 (MPEG, Layer 3)
* and U-Law formats (codecs).
*
* ManagerQuery always prints the version of JMF, caching directory,
* and hints prior to any other output.
*
* @author Spike Barlow
****************************************************************/
import javax.media.*;
import javax.media.protocol.*;
import javax.media.format.*;
import java.util.*;

public class ManagerQuery {
  ///////////////////////////////////////////////////
  // Constants to facilitate selection of the
  // approprite get*List() method.
  ///////////////////////////////////////////////////
  public static final int HANDLERS = 1;
  public static final int PROCESSORS = 2;
  public static final int DATASOURCES = 3;
  ///////////////////////////////////////////////////////
  // Array containing all the content types that JMF2.1.1
  // supports. This is used when the user provides no
  // command-line arguments in order to generate a
  // complete list of support for all the content types.
  /////////////////////////////////////////////////////////
  private static final String[] CONTENTS = {ContentDescriptor.CONTENT_UNKNOWN,
      ContentDescriptor.MIXED, ContentDescriptor.RAW,
ContentDescriptor.RAW_RTP, FileTypeDescriptor.AIFF,
FileTypeDescriptor.BASIC_AUDIO, FileTypeDescriptor.GSM,
      FileTypeDescriptor.MIDI, FileTypeDescriptor.MPEG,
FileTypeDescriptor.MPEG_AUDIO, FileTypeDescriptor.MSVIDEO,
FileTypeDescriptor.QUICKTIME, FileTypeDescriptor.RMF,
FileTypeDescriptor.VIVO, FileTypeDescriptor.WAVE,
      VideoFormat.CINEPAK, VideoFormat.H261, VideoFormat.H263,
VideoFormat.H261_RTP, VideoFormat.H263_RTP,
VideoFormat.INDEO32, VideoFormat.INDEO41, VideoFormat.INDEO50,
      VideoFormat.IRGB, VideoFormat.JPEG, VideoFormat.JPEG_RTP,
VideoFormat.MJPEGA,  VideoFormat.MJPEGB, VideoFormat.MJPG,
VideoFormat.MPEG_RTP, VideoFormat.RGB, VideoFormat.RLE, VideoFormat.SMC,
VideoFormat.YUV, AudioFormat.ALAW,
      AudioFormat.DOLBYAC3, AudioFormat.DVI, AudioFormat.DVI_RTP,
AudioFormat.G723, AudioFormat.G723_RTP, AudioFormat.G728,
AudioFormat.G728_RTP, AudioFormat.G729, AudioFormat.G729_RTP,
AudioFormat.G729A, AudioFormat.G729A_RTP, AudioFormat.GSM,
      AudioFormat.GSM_MS, AudioFormat.GSM_RTP, AudioFormat.IMA4,
AudioFormat.IMA4_MS, AudioFormat.LINEAR, AudioFormat.MAC3,
AudioFormat.MAC6, AudioFormat.MPEG, AudioFormat.MPEG_RTP,
AudioFormat.MPEGLAYER3, AudioFormat.MSADPCM,
AudioFormat.MSNAUDIO, AudioFormat.MSRT24,
AudioFormat.TRUESPEECH, AudioFormat.ULAW, AudioFormat.ULAW_RTP,
AudioFormat.VOXWAREAC10, AudioFormat.VOXWAREAC16,
AudioFormat.VOXWAREAC20, AudioFormat.VOXWAREAC8,
AudioFormat.VOXWAREMETASOUND, AudioFormat.VOXWAREMETAVOICE,
AudioFormat.VOXWARERT29H, AudioFormat.VOXWARETQ40,
      AudioFormat.VOXWARETQ60, AudioFormat.VOXWAREVR12,
AudioFormat.VOXWAREVR18};
  ////////////////////////////////////
  // The protocols that JMF supports.
  ///////////////////////////////////
  private static final String[] PROTOCOLS = { "ftp", "file", "rtp",
"http"};


/***************************************************************
* Return a String being a list of all hints settings.
****************************************************************/
public static String getHints() {

  return "	Security: " + Manager.getHint(Manager.MAX_SECURITY) +
      "
	Caching: " + Manager.getHint(Manager.CACHING) +
      "
	Lightweight Renderer: " +
Manager.getHint(Manager.LIGHTWEIGHT_RENDERER) +
      "
	Plug-in Player: " +
Manager.getHint(Manager.PLUGIN_PLAYER);
}


/******************************************************************
* Produce a list of all classes that support the content types or
* protocols passed to the method. The list is returned as a formatted
* String, while the 2nd parameter (which) specifies whether it is
* Player (Handler), Processor, or DataSource classes.
********************************************************************/
public static String getHandlersOrProcessors(String[] contents,
int which) {
  String  str="";
  Vector  classes;
  int     NUM_PER_LINE = 1;
  String  LEADING = "	    ";
  String  SEPARATOR = "  ";

  if (contents==null)
    return null;

  ///////////////////////////////////////////////////////////////////
  // Generate a separate list for each content-type/protocol
  //specified.
  ///////////////////////////////////////////////////////////////////
  for (int i=0;i<contents.length;i++) {
    str=str + "	" + contents[i] + ":
";
    if (which==HANDLERS)
      classes = Manager.getHandlerClassList(contents[i]);
    else if (which==PROCESSORS)
      classes = Manager.getProcessorClassList(contents[i]);
    else
      classes = Manager.getDataSourceList(contents[i]);
    if (classes==null)
      str = str + "	    <None>
";
    else
      str = str + formatVectorStrings(classes,LEADING,NUM_PER_LINE,
                    SEPARATOR);
  }
  return str;
}


/********************************************************************
* Get a list of all Handler (Player) classes that support each of the
* formats (content types).
********************************************************************/
public static String getHandlers() {

  return getHandlersOrProcessors(CONTENTS,HANDLERS);
}


/********************************************************************
* Get a list of all Processor classes that support each of the
* formats (content types).
********************************************************************/
public static String getProcessors() {
  return getHandlersOrProcessors(CONTENTS,PROCESSORS);
}


/********************************************************************
* Get a list of all DataSources classes that support each of the
* protocols.
********************************************************************/

public static String getDataSources() {
  return getHandlersOrProcessors(PROTOCOLS,DATASOURCES);
}

/********************************************************************
* Format the Vector of Strings returned by the get*List() methods
* into a single String. A simple formatting method.
********************************************************************/
public static String formatVectorStrings(Vector vec, String leading,
int count, String separator) {
  String  str=leading;


  for (int i=0;i<vec.size();i++) {
    str = str + (String)vec.elementAt(i);
    if ((i+1)==vec.size())
      str = str + "
";
    else if ((i+1)%count==0)
      str = str + "
" + leading;
    else
      str = str + separator;
  }
  return str;
}


/***********************************************************
* Produce a list showing total support (i.e., Player,
* Processors, and DataSinks) for all content types and
* protocols.
************************************************************/
public static void printTotalList() {
  System.out.println("
Player Handler Classes:");
  System.out.println(getHandlers());
  System.out.println("
Processor Class List:");
  System.out.println(getProcessors());
  System.out.println("
DataSink Class List: ");
  System.out.println(getDataSources());
}


/********************************************************************
* Main method. Produce a version and hints report. Then if no command
* line arguments produce a total class list report. Otherwise process
* the command line arguments and produce a report on their basis.
********************************************************************/
public static void main(String args[]) {

  System.out.println("JMF: " + Manager.getVersion());
  String cacheArea = Manager.getCacheDirectory();
  if (cacheArea==null)
    System.out.println("No cache directory specified.");
  else
    System.out.println("Cache Directory: " + cacheArea);
  System.out.println("Hints:");
  System.out.println(getHints());

  // No command-line arguments. Make a total report.
  if (args==null || args.length==0)
    printTotalList();
  else {

    // Command-line. Process flags and then support to be
    // queried upon in order to generate appropriate report.
    String  header="";
    int     whichCategory = 0;
    String[]  interested;
    int     i;
    int     start;
    if (args[0].equalsIgnoreCase("-h")) {
      header = "
Player Handler Classes: ";
      whichCategory = HANDLERS;
    }
    else if (args[0].equalsIgnoreCase("-p")) {
      header = "
Processor Class List:";
      whichCategory = PROCESSORS;
    }
    else if (args[0].equalsIgnoreCase("-d")) {
      header = "
DataSink Class List: ";
      whichCategory = DATASOURCES;
    }
    if (whichCategory==0) {
      whichCategory = HANDLERS;
      header = "
Player Handler Classes: ";
      interested = new String[args.length];
      start = 0;
    }
    else {
      interested = new String[args.length-1];
      start = 1;
    }
    for (i=start;i<args.length;i++)
      interested[i-start] = args[i];
    System.out.println(header);
    System.out.println(getHandlersOrProcessors(interested,whichCategory));
  }
}
}

In order to specify a particular query, the formats or protocols in question are provided as command-line arguments to the application. An initial flag specifier supports Processors (-p), DataSources (-d), or Players (handlers, hence -h), which are being examined. Failure to specify a flag is interpreted as being a query about Players, whereas a lack of any command-line arguments is interpreted as a query about Player, Processor, and DataSource support for all formats and protocols. Listing 8.5 shows two runs of the program. The first in which Processor classes supporting the mpg (MPEG) and avi (AVI) content types are listed. The second in which handlers (Players) classes supporting http (Hypertext Transfer Protocol) are listed.

Listing 8.5 Two Runs of the ManagerQuery Application Show How It Can Be Employed and the Output Produced
D:JMFBookCode>java ManagerQuery -p mpg avi
JMF: 2.1.1
Cache Directory: C:WINDOWSTEMP
Hints:
        Security: false
        Caching: true
        Lightweight Renderer: false
        Plug-in Player: false

Processor Class List:
        mpg:
            media.processor.mpg.Handler
            javax.media.processor.mpg.Handler
            com.sun.media.processor.mpg.Handler
            com.ibm.media.processor.mpg.Handler
        avi:
            media.processor.avi.Handler
            javax.media.processor.avi.Handler
            com.sun.media.processor.avi.Handler
            com.ibm.media.processor.avi.Handler

D:JMFBookCode>java ManagerQuery -h http
JMF: 2.1.1
Cache Directory: C:WINDOWSTEMP
Hints:
        Security: false
        Caching: true
        Lightweight Renderer: false
        Plug-in Player: false

Player Handler Classes:
        http:
            media.content.http.Handler
            javax.media.content.http.Handler
            com.sun.media.content.http.Handler
            com.ibm.media.content.http.Handler

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset