FileDocCategorySizeDatePackage
BasicSourceModule.javaAPI DocJMF 2.1.1e24303Mon May 12 12:20:48 BST 2003com.sun.media

BasicSourceModule

public class BasicSourceModule extends BasicModule implements Duration, Positionable
MediaSource is a module which have OutputConnectors and no InputConnectors. It receives data from PullSourceStream and stream the data to the downstream modules (in case of PushSourceStream an adapter should be written Push2Pull such as the RTP "bucket" adapter).
MediaSource are typically not threaded and use Pull protocol (URL connection is really threaded but everything goes "under the hood" so we refer to it as unthreaded one).
This class support data caching in either memory or disk.
When an attemped read would block, the Player would Restart (in order to fetch data). We need the level 3 design for:
  • container file format (such as WAV or AVI) header parsers
  • container file format Tracks separator. For each Track OutputConnector is generated
  • fixed frame size codecs (e.g. GSM or G.723) need to expose: time -> offset and offset -> time conversions. How detection of the codec mode is handled (G.723 Lo and Hi) for seek?
    A good candidate for putting those methods is the Format class or the Codec class
  • Variable frame size file format (such as MPEG system layer) should provide a method to perform the seek.

Fields Summary
PlaybackEngine
engine
protected DataSource
source
protected Demultiplexer
parser
protected Track[]
tracks
protected SourceThread[]
loops
protected String[]
connectorNames
protected long
bitsRead
private static JMFSecurity
jmfSecurity
private static boolean
securityPrivelege
private Method[]
m
private Class[]
cl
private Object[]
args
Object
resetSync
protected boolean
started
protected SystemTimeBase
systemTimeBase
protected long
lastSystemTime
protected long
originSystemTime
protected long
currentSystemTime
protected Time
lastPositionSet
RTPTimeBase
rtpMapperUpdatable
RTPTimeBase
rtpMapper
long
currentRTPTime
long
oldOffset
boolean
rtpOffsetInvalid
String
cname
public String
errMsg
int
latencyTrack
Constructors Summary
protected BasicSourceModule(DataSource ds, Demultiplexer demux)

        source = ds;
        parser = demux;

        SourceStream stream = null;
        if ( source instanceof PullDataSource) {
            stream =  ((PullDataSource) source).getStreams()[0];
        } else  if ( source instanceof PushDataSource) {
            stream = ((PushDataSource) source).getStreams()[0];
        }

    
Methods Summary
public voidabortPrefetch()

        doStop();
    
public voidabortRealize()

        parser.stop();
        parser.close();
    
protected booleancheckAllPaused()

        for (int i = 0; i < loops.length; i++) {
            if (tracks[i].isEnabled() && loops[i] != null && !loops[i].isPaused())
		return false;
        }
	return true;
    
public voidcheckLatency()


       

	// If a track is already assigned for the latency computation,
	// use it.
	if (latencyTrack > -1) {
	    if (tracks[latencyTrack].isEnabled() && loops[latencyTrack] != null) {
		loops[latencyTrack].checkLatency = true;
		return ;
	    } else
		latencyTrack = -1;
	}

	// Select a track to compute the latency. 
        for (int i = 0; i < tracks.length; i++) {

	    if (!tracks[i].isEnabled())
		continue;

	    latencyTrack = i;
            if (tracks[i].getFormat() instanceof VideoFormat) {
		// If there's a video track, use that.
                break;
            }
        }

	if (latencyTrack > -1 && loops[latencyTrack] != null)
	    loops[latencyTrack].checkLatency = true;
    
protected static javax.media.DemultiplexercreateDemultiplexer(javax.media.protocol.DataSource ds)
Create a plugin parser based on the input DataSource.


        // Create the parser based on the DataSource's mime type.
        ContentDescriptor cd = new ContentDescriptor(ds.getContentType());
        Vector cnames = PlugInManager.getPlugInList(cd, null, PlugInManager.DEMULTIPLEXER); 
        Class cls;
        Demultiplexer parser = null;
	IOException ioe = null;
	IncompatibleSourceException ise = null;
        for (int i = 0; i < cnames.size(); i++) {
            try {
		// cls = Class.forName((String)cnames.elementAt(i));
		cls = BasicPlugIn.getClassForName((String)cnames.elementAt(i));
                Object p = cls.newInstance();

                if (p instanceof Demultiplexer) {
                    parser = (Demultiplexer)p;
                    try {
                        parser.setSource(ds);
                    } catch (IOException e) {
                        parser = null;
			ioe = e;
			continue;
                    } catch (IncompatibleSourceException e) {
                        parser = null;
			ise = e;
                        continue;
                    }
                    break;
                }
            } catch (ClassNotFoundException e) {
            } catch (InstantiationException e) {
            } catch (IllegalAccessException e) {
            }
        }
	if (parser == null) {
	    if (ioe != null)
		throw ioe;
            if (ise != null)
        	throw ise;
	}
        return parser;
    
public static com.sun.media.BasicSourceModulecreateModule(javax.media.protocol.DataSource ds)



     
        try {
            jmfSecurity = JMFSecurityManager.getJMFSecurity();
            securityPrivelege = true;
        } catch (SecurityException e) {
        }
    
        Demultiplexer parser = createDemultiplexer(ds);
	if (parser == null)
	    return null;
	return new BasicSourceModule(ds, parser);
    
com.sun.media.SourceThreadcreateSourceThread(int idx)
Create the source loop thread.


	SourceThread thread = null;
	MyOutputConnector oc = 
		(MyOutputConnector)getOutputConnector(connectorNames[idx]);

        if (oc == null || oc.getInputConnector() == null) {
	    tracks[idx].setEnabled(false);
	    return null;
	}

	if ( jmfSecurity != null ) {
	    String permission = null;
	    try {
		if (jmfSecurity.getName().startsWith("jmf-security")) {
		    permission = "thread";
		    jmfSecurity.requestPermission(m, cl, args,
						  JMFSecurity.THREAD);
		    m[0].invoke(cl[0], args[0]);
		    
		    permission = "thread group";
		    jmfSecurity.requestPermission(m, cl, args,
						  JMFSecurity.THREAD_GROUP);
		    m[0].invoke(cl[0], args[0]);
		} else if (jmfSecurity.getName().startsWith("internet")) {
		    PolicyEngine.checkPermission(PermissionID.THREAD);
		    PolicyEngine.assertPermission(PermissionID.THREAD);
		}
	    } catch (Throwable e) {
		if (JMFSecurityManager.DEBUG) {
		    System.err.println( "Unable to get " + permission +
					" privilege  " + e);
		}
		securityPrivelege = false;
		// TODO: Do the right thing if permissions cannot be obtained.
		// User should be notified via an event
	    }
	}

 	if ( (jmfSecurity != null) && (jmfSecurity.getName().startsWith("jdk12"))) {

	    try {
		Constructor cons = CreateSourceThreadAction.cons;
		Constructor pcons = jdk12PriorityAction.cons;

		thread = (SourceThread) jdk12.doPrivM.invoke(
                                           jdk12.ac,
 					  new Object[] {
 					  cons.newInstance(
 					   new Object[] {
					      SourceThread.class,
                                               this, oc, new Integer(idx)
                                           })});
		
		// Use this rough priority scheme for now.
		int priority;
		if (tracks[idx].getFormat() instanceof AudioFormat)
		    priority = MediaThread.getAudioPriority();
		else
		    priority = MediaThread.getVideoPriority();
		    
		thread.useVideoPriority();

		jdk12.doPrivM.invoke(
				     jdk12.ac,
				     new Object[] {
 					  pcons.newInstance(
 					   new Object[] {
                                               thread,
                                               new Integer(priority)
                                           })});

	    } catch (Exception e) { 
		thread = null;
	    }

 	} else {
	    thread = new SourceThread(this, oc, idx); 
		
	    // Use this rough priority scheme for now.
	    if (tracks[idx].getFormat() instanceof AudioFormat)
		thread.useAudioPriority();
	    else
		thread.useVideoPriority();
 	}

	if (thread == null) {
	    // failed to create the thread for some reason.
	    tracks[idx].setEnabled(false);
	}

	return thread;
    
public voiddoClose()

        parser.close();
        if (tracks == null)
           return;
        // Kill the threads.
        for (int i = 0; i < tracks.length; i++) {
	    if (loops[i] != null)
        	loops[i].kill();
        }

	if (rtpMapperUpdatable != null) {
	    RTPTimeBase.returnMapperUpdatable(rtpMapperUpdatable);
	    rtpMapperUpdatable = null;
	}
    
public voiddoDealloc()

    
public voiddoFailedPrefetch()

    
public voiddoFailedRealize()

        parser.stop();
        parser.close();
    
public booleandoPrefetch()

        super.doPrefetch();
        return true;
    
public booleandoRealize()
Parsed in the input to get the track info. This should be called in Player.realize() or Processor.connect().


                          
       
        try {
            parser.open();
        } catch (ResourceUnavailableException e) {
	    errMsg = "Resource unavailable: " + e.getMessage();
            return false;
	}

	try {
	    parser.start();
            tracks = parser.getTracks();
        } catch (BadHeaderException e) {
	    errMsg = "Bad header in the media: " + e.getMessage();
	    parser.close();
            return false;
        } catch (IOException e) {
	    errMsg = "IO exception: " + e.getMessage();
	    parser.close();
            return false;
        }

        // Guard against some menace parser.
        if (tracks == null || tracks.length == 0) {
	    errMsg = "The media has 0 track";
	    parser.close();
            return false;
	}

        MyOutputConnector oc;

        if ( /*securityPrivelege &&*/ (jmfSecurity != null) ) {
            String permission = null;
            try {
		if (jmfSecurity.getName().startsWith("jmf-security")) {
		    permission = "thread";
		    jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD);
		    m[0].invoke(cl[0], args[0]);
		    
		    permission = "thread group";
		    jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD_GROUP);
		    m[0].invoke(cl[0], args[0]);
		} else if (jmfSecurity.getName().startsWith("internet")) {
		    PolicyEngine.checkPermission(PermissionID.THREAD);
		    PolicyEngine.assertPermission(PermissionID.THREAD);
		}
            } catch (Exception e) {
		if (JMFSecurityManager.DEBUG) {
		    System.err.println("Unable to get " + permission +
				       " privilege  " + e);
		}
                securityPrivelege = false;
                // TODO: Do the right thing if permissions cannot be obtained.
                // User should be notified via an event
            }
        }

	loops = new SourceThread[tracks.length];
	connectorNames = new String[tracks.length];
	for (int i = 0; i < tracks.length; i++) {
	    oc = new MyOutputConnector(tracks[i]);
	    oc.setProtocol(Connector.ProtocolPush);
	    oc.setSize(1);
	    connectorNames[i] = tracks[i].toString();
	    registerOutputConnector(tracks[i].toString(), oc);
	    loops[i] = null;
	}

	engine = (PlaybackEngine)getController();

	// For RTP, we don't stop the parser.  This prevents
	// the RTP buffer Q from being flushed.  Flushing the
	// buffer Q will flush the initial chunks of data.  That's
	// bad for H.261 which requires the initial key frame.
	if (engine == null || !engine.isRTP())
	    parser.stop();

        return true;
    
public voiddoStart()


	lastSystemTime = systemTimeBase.getNanoseconds();
	originSystemTime = currentSystemTime;

	rtpOffsetInvalid = true;

        super.doStart();
        try {
            parser.start();
        } catch (IOException e) { }

        for (int i = 0; i < loops.length; i++) {
            // Start the track only if the track is enabled and the
            // output connector is connected to an input.
            if (tracks[i].isEnabled()) {
		if (loops[i] == null &&
		    (loops[i] = createSourceThread(i)) == null) {
			continue;
		}
                loops[i].start();
	    }
        }

	started = true;
    
public voiddoStop()
This is a blocking pause.

	// We don't stop the source until prefetch is done.
	started = false;
    
public longgetBitsRead()

        return bitsRead;
    
public java.lang.ObjectgetControl(java.lang.String s)

	return parser.getControl(s);
    
public java.lang.Object[]getControls()

	return parser.getControls();
    
public javax.media.DemultiplexergetDemultiplexer()

        return parser;
    
public javax.media.TimegetDuration()

        return parser.getDuration();
    
public java.lang.String[]getOutputConnectorNames()
Return an array of strings containing this media module's output port names.

        return connectorNames;
    
public booleanisPositionable()

        return parser.isPositionable();
    
public booleanisRandomAccess()

        return parser.isRandomAccess();
    
public voidpause()
This is essentially a non-blocking version of doStop.

      synchronized (resetSync) {
        for (int i = 0; i < loops.length; i++) {
            if (tracks[i].isEnabled() && loops[i] != null && !loops[i].resetted)
                loops[i].pause();
        }
        parser.stop();
      }
    
public voidprocess()

    
booleanreadHasBlocked()

        if (loops == null) return false;
        for (int i = 0; i < loops.length; i++) {
            if (loops[i] != null && loops[i].readBlocked)
                return true;
        }
        return false;
    
public voidreset()

      synchronized (resetSync) {
	super.reset();
        for (int i = 0; i < loops.length; i++) {
            if (tracks[i].isEnabled()) {
		if (loops[i] == null && 
		    (loops[i] = createSourceThread(i)) == null) {
			continue;
		}
		loops[i].resetted = true;
                loops[i].start();
	    }
        }
      }
    
public voidresetBitsRead()

        bitsRead = 0;
    
public voidsetFormat(com.sun.media.Connector connector, javax.media.Format format)

    
public javax.media.TimesetPosition(javax.media.Time when, int rounding)

        Time t = parser.setPosition(when, rounding);

	// This is a hack for MPEG/RTP right now.  The MPEG
	// packetizers uses the header attribute in the Buffer object
	// to store the last position (media time) set.  It used to
	// do that in the MPEG parser.  But it needs to be done
	// for all cases since transcoding can occur from any parser
	// to the MPEG packetizers.
	if (lastPositionSet.getNanoseconds() == t.getNanoseconds())
	    lastPositionSet = new Time(t.getNanoseconds() + 1);
	else
	    lastPositionSet = t;
	return t;