FileDocCategorySizeDatePackage
BasicRendererModule.javaAPI DocJMF 2.1.1e29788Mon May 12 12:20:48 BST 2003com.sun.media

BasicRendererModule

public class BasicRendererModule extends BasicSinkModule implements RTPTimeReporter
BasicRenderer is a module which have InputConnectors and no OutputConnectors. It receives data from its input connector and put its output in output device such as file, URL, screen, audio device, output DataSource or null.
MediaRenderers can be either Pull driven (as AudioPlayer) or Push driven (as File renderer). VideoRenderer might be implemented as either Push or Pull.
MediaRenderers are stopAtTime aware (so that the audio renderer would stop at the correct time) and are responsible to stop the player at the required time (no separate thread for poling TimeBase).
There is no need to define buffers allocation and connectors behavior here, as it is done in module.

Common functionality of renderers would be put here as we start the implementation
We need the level 3 design to continue working on this class

Fields Summary
protected PlaybackEngine
engine
protected Renderer
renderer
protected InputConnector
ic
protected int
framesPlayed
protected float
frameRate
protected boolean
framesWereBehind
protected boolean
prefetching
protected boolean
started
private boolean
opened
private int
chunkSize
private long
prefetchedAudioDuration
private long
lastDuration
private RTPTimeBase
rtpTimeBase
private String
rtpCNAME
RenderThread
renderThread
private static JMFSecurity
jmfSecurity
private static boolean
securityPrivelege
private Method[]
m
private Class[]
cl
private Object[]
args
private Object
prefetchSync
private ElapseTime
elapseTime
private long
LEEWAY
private long
lastRendered
private boolean
failed
private boolean
notToDropNext
private Buffer
storedBuffer
private boolean
checkRTP
private boolean
noSync
final float
MAX_RATE
final float
RATE_INCR
final int
FLOW_LIMIT
boolean
overMsg
int
overflown
float
rate
long
systemErr
static final long
RTP_TIME_MARGIN
boolean
rtpErrMsg
long
lastTimeStamp
static final int
MAX_CHUNK_SIZE
AudioFormat
ulawFormat
AudioFormat
linearFormat
Constructors Summary
protected BasicRendererModule(Renderer r)


     
	try {
	    jmfSecurity = JMFSecurityManager.getJMFSecurity();
	    securityPrivelege = true;
	} catch (SecurityException e) {
	}
    
	setRenderer(r);
	ic = new BasicInputConnector();
	if (r instanceof javax.media.renderer.VideoRenderer)
	    ic.setSize(4);
	else
	    ic.setSize(1);
	ic.setModule(this);
	registerInputConnector("input", ic);
	setProtocol(Connector.ProtocolSafe);
    
Methods Summary
public voidabortPrefetch()

	renderThread.pause();
	renderer.close();
	prefetching = false;
	opened = false;
    
private intcomputeChunkSize(javax.media.Format format)

        

	// Break up the data if it's linear or ulaw audio.
	if (format instanceof AudioFormat && 
	    (ulawFormat.matches(format) || linearFormat.matches(format))) {

	    AudioFormat af = (AudioFormat)format;
	    int units = af.getSampleSizeInBits() * af.getChannels() / 8;
	    if (units == 0) // sample size < 1 byte.
		units = 1;
	    int chunks = (int)af.getSampleRate() * units / MAX_CHUNK_SIZE;

	    // Chunks should be in multiples of the independent units.
	    return (int)(chunks / units * units);
	}

	return Integer.MAX_VALUE;
    
public voiddoClose()

	renderThread.kill();
	if (renderer != null)
	    renderer.close();
	if (rtpTimeBase != null) {
	    RTPTimeBase.remove(this, rtpCNAME);
	    rtpTimeBase = null;
	}
    
public voiddoDealloc()

	renderer.close();
    
public voiddoFailedPrefetch()

	renderThread.pause();
	renderer.close();
	opened = false;
	prefetching = false;
    
public booleandoPrefetch()

	super.doPrefetch();
	if (!opened) {
	    try {
		renderer.open();
	    } catch (ResourceUnavailableException e) {
		prefetchFailed = true;
		return false;
	    }
	    prefetchFailed = false;
	    opened = true;

	}

	if (! ((PlaybackEngine)controller).prefetchEnabled)
	    return true;

	prefetching = true;

	// We also need to start the render thread.  Otherwise, it won't
	// get the first prefetch frame. 
	renderThread.start();

	return true;
    
protected booleandoProcess()
The loop to process the data. It handles the getting and putting back of the data buffers. It in turn calls scheduleBuffer(Buffer) to do the bulk of processing.


                                     
       

	// Notify the engine if stop time has been reached.
	if ((started || prefetching) && 
	    stopTime > -1 && elapseTime.value >= stopTime) {
	    if (renderer instanceof Drainable)
		((Drainable)renderer).drain();
	    doStop();
	    if (moduleListener != null)
		moduleListener.stopAtTime(this);
	}

	Buffer buffer;

	if (storedBuffer != null)
	    buffer = storedBuffer;
	else {
	    buffer = ic.getValidBuffer();
	    /*
	    System.err.println("TS: " + buffer.getTimeStamp() + 
		" dur: " + buffer.getDuration() + 
		" len: " + buffer.getLength() + 
		" seq: " + buffer.getSequenceNumber() + 
		" eom: " + buffer.isEOM() +
		" discard: " + buffer.isDiscard());
	     */
	}

	if (!checkRTP) {

	    // If this is playing back from RTP, we'll get an RTPTimeBase.
	    // This test cannot be performed at realize time since the
	    // actual rtp DataSource may not be available at that time
	    // for RTSP playback.

	    if ((buffer.getFlags() & Buffer.FLAG_RTP_TIME) != 0) {

		String key = engine.getCNAME();
		if (key != null) {
		    rtpTimeBase = RTPTimeBase.find(this, key);
		    rtpCNAME = key;
		    // Set this as the master if it is playing audio.
		    if (ic.getFormat() instanceof AudioFormat) {
			Log.comment("RTP master time set: " + renderer + "\n");
			//System.err.println("RTP sync kicks in");
			rtpTimeBase.setMaster(this);
		    }
		    checkRTP = true;
		    noSync = false;
		} else {
		    // There's no cname association yet.  We can't do any
		    // synchronization at this point.
		    // We'll have to keep checking back again.
		    noSync = true;
		}
	    } else
		checkRTP = true;
	}

	lastTimeStamp = buffer.getTimeStamp();

	// Check if we are in the resetted state.
	if (failed || resetted) {

	    // Check if the input buffer contains the zero-length
	    // flush flag.  If so, we are almost done.
	    if ((buffer.getFlags() & Buffer.FLAG_FLUSH) != 0) {
		resetted = false;
		renderThread.pause();

		// Notify the engine if the module has done processing.
		if (moduleListener != null)
		    moduleListener.resetted(this);
	    }

	    // In the resetted state, we won't process any of the
	    // data.  We'll just return the buffers unprocessed.
	    storedBuffer = null;
	    ic.readReport();
	    return true;
	}

	if (PlaybackEngine.DEBUG) jmd.moduleIn(this, 0, buffer, true);

	// Schedule the buffer to be rendered.
	boolean rtn = scheduleBuffer(buffer);

	// Handle EOM
	// Also need to make sure this buffer is fully consumed, i.e., 
	// the storedBuffer is not set.
	if (storedBuffer == null && buffer.isEOM()) {

	    if (prefetching)
		donePrefetch();

	    // Eventhough we've received EOM, we haven't finished
	    // processing yet.  We'll need to sleep till its PT.
	    // Otherwise, for low FR movies, the last frame will
	    // not be presented to its full duration.
	    if ((buffer.getFlags() & Buffer.FLAG_NO_WAIT) == 0 &&
		 buffer.getTimeStamp() > 0 && buffer.getDuration() > 0 &&
		 buffer.getFormat() != null &&
		 !(buffer.getFormat() instanceof AudioFormat) &&
		 !noSync) {
		waitForPT(buffer.getTimeStamp() + lastDuration);
	    }

	    storedBuffer = null;
	    ic.readReport();

	    if (PlaybackEngine.DEBUG) jmd.moduleIn(this, 0, buffer, false);
	    doStop();
	    if (moduleListener != null)
		moduleListener.mediaEnded(this);

	    return true;
	}

	// If we are fully done with this buffer, return it.
	if (storedBuffer == null)
	    ic.readReport();

	if (PlaybackEngine.DEBUG) jmd.moduleIn(this, 0, buffer, false);

	return rtn;
    
public booleandoRealize()

	chunkSize = computeChunkSize(ic.getFormat());

	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 (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 = CreateWorkThreadAction.cons;
		renderThread = (RenderThread) jdk12.doPrivM.invoke(
                                           jdk12.ac,
 					  new Object[] {
 					  cons.newInstance(
 					   new Object[] {
                                               RenderThread.class,
					       BasicRendererModule.class,
					       this
                                           })});
	    } catch (Exception e) {
	    }
 	} else {
	    renderThread = new RenderThread(this);
 	}

	engine = (PlaybackEngine)getController();

	return true;
    
public voiddoStart()

	super.doStart();
	// Clocked renderer is handled by the super.doStart().
	if (!(renderer instanceof Clock))
	    renderer.start();
	prerolling = false;
	started = true;

	synchronized (prefetchSync) {
	    prefetching = false;
	    renderThread.start();
	}
    
public voiddoStop()

	started = false;
	prefetching = true;
	super.doStop();
	// Clocked renderer is handled by the super.doStop().
	if (renderer != null && !(renderer instanceof Clock))
	    renderer.stop();
    
private voiddonePrefetch()
Handles the aftermath of prefetching.


	synchronized (prefetchSync) {
	    if (!started && prefetching)
		renderThread.pause();
	    prefetching = false;
	}

	if (moduleListener != null)
	    moduleListener.bufferPrefetched(this);
    
public voiddoneReset()

	renderThread.pause();
    
public java.lang.ObjectgetControl(java.lang.String s)

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

	return renderer.getControls();
    
public intgetFramesPlayed()

	return framesPlayed;
    
public longgetRTPTime()

	if (ic.getFormat() instanceof AudioFormat) {
	   if (renderer instanceof AudioRenderer) {
		/*
		  System.err.println("rtpTime[audio]: " + 
			lastTimeStamp + " latency: " 
			+ ((AudioRenderer)renderer).getLatency() + " TS: " +
			    (lastTimeStamp
				- ((AudioRenderer)renderer).getLatency()));
		 */
		return lastTimeStamp - ((AudioRenderer)renderer).getLatency();
	   } else {
		//System.err.println("rtpTime[audio]: " + lastTimeStamp);
		return lastTimeStamp;
	   }
	} else {
	   //System.err.println("rtpTime[video]: " + lastTimeStamp);
	   return lastTimeStamp;
	}
    
public javax.media.RenderergetRenderer()

	return renderer;
    
private longgetSyncTime(long pts)


        
	if (rtpTimeBase != null) {
	    // If we are the master, we don't need to request time
	    // from the rtpTimeBase.
	    if (rtpTimeBase.getMaster() == getController())
		return pts;
	    long ts = rtpTimeBase.getNanoseconds();
	    // Cannot sync beyond a limit.
	    if (ts > pts + RTP_TIME_MARGIN || ts < pts - RTP_TIME_MARGIN) {
		/*
		System.err.println("pts and mts too different: ts = " + 
			ts/1000000L + " pts = " + 
			pts/1000000L + " diff = " + 
			(ts - pts)/1000000L);
		*/
		if (!rtpErrMsg) {
		    Log.comment("Cannot perform RTP sync beyond a difference of: " + (ts - pts)/1000000L + " msecs.\n");
		    rtpErrMsg = true;
		}
		//System.err.println("No RTP Sync: " + (ts - pts)/1000000L + " msecs.\n");
		return pts;
	    } else
		return ts;
	} else
	    return getMediaNanoseconds();
    
private booleanhandleFormatChange(javax.media.Format format)
Handles mid-stream format change.

	// The format is changed mid-stream!
	if (!reinitRenderer(format)) {
	    // Failed.
	    storedBuffer = null;
	    failed = true;
	    if (moduleListener != null)
		moduleListener.formatChangedFailure(this, ic.getFormat(), format);
	    return false;
	}

	Format oldFormat = ic.getFormat();
	ic.setFormat(format);
	if (moduleListener != null)
	    moduleListener.formatChanged(this, oldFormat, format);

	if (format instanceof VideoFormat) {
	    float fr = ((VideoFormat)format).getFrameRate();
	    if (fr != VideoFormat.NOT_SPECIFIED)
		frameRate = fr;
	}

	return true;
    
protected booleanhandlePreroll(javax.media.Buffer buf)
Handle the prerolling a buffer. It will preroll until the media has reach the current media time before displaying.


	if (buf.getFormat() instanceof AudioFormat) {

	    if (!hasReachAudioPrerollTarget(buf))
		return false;

	} else if ((buf.getFlags() & Buffer.FLAG_NO_SYNC) != 0 || 
	    buf.getTimeStamp() < 0) {

	    // The data is non-time specific.
	    // Deliberately empty at this point.

	} else if (buf.getTimeStamp() < getSyncTime(buf.getTimeStamp())) {
	    // The data is time-specific and it hasn't yet reached the
	    // target media time.  So we are skipping it.

	    //System.err.println("preroll video: " + buf.getTimeStamp());
	    return false;
	}

	/*
	if (buf.getFormat() instanceof AudioFormat)
	    System.err.println("done prerolling audio: " + buf.getLength());
	else
	    System.err.println("done prerolling video: " + buf.getLength());
	*/

	// The preroll target has been reached.
	prerolling = false;

	return true;
    
private booleanhasReachAudioPrerollTarget(javax.media.Buffer buf)
Return true if given the input buffer, the audio will reach the target preroll time -- the current media time.


	//System.err.println("preroll audio: " + buf.getLength());
	long target = getSyncTime(buf.getTimeStamp());

	elapseTime.update(buf.getLength(), buf.getTimeStamp(), buf.getFormat());

	if (elapseTime.value >= target) {
	    long remain = ElapseTime.audioTimeToLen(
					elapseTime.value - target,
					(AudioFormat)buf.getFormat());
	    int offset = buf.getOffset() + buf.getLength() - (int)remain;
	    if (offset >= 0) {
		buf.setOffset(offset);
		buf.setLength((int)remain);
	    }

	    elapseTime.setValue(target);

	    return true;
	}

	return false;
    
public booleanisThreaded()

	return true;
    
protected voidprocess()

    
public intprocessBuffer(javax.media.Buffer buffer)
Break down one larger buffer into smaller pieces so the processing won't take that long to block.


                          
        
	int remain = buffer.getLength();
	int offset = buffer.getOffset();
	int len, rc = PlugIn.BUFFER_PROCESSED_OK;
	boolean isEOM = false;

	// Data flow management.  If the FLAG_BUF_OVERFLOWN flag
	// is set, we'll try to speed up the renderer to catch up.
	// This is beneficial for streaming media when the server
	// clock is faster than the client clock.
	if (renderer instanceof Clock) {
	    if ((buffer.getFlags() & buffer.FLAG_BUF_OVERFLOWN) != 0)
		overflown++;
	    else
		overflown--;

	    if (overflown > FLOW_LIMIT) {

		if (rate < MAX_RATE) {
		    rate += RATE_INCR;
		    renderer.stop();
		    ((Clock)renderer).setRate(rate);
		    renderer.start();
		    if (!overMsg) {
			Log.comment("Data buffers overflown.  Adjust rendering speed up to 5 % to compensate"); 
			overMsg = true;
		    }
		}

		overflown = FLOW_LIMIT / 2;

	    } else if (overflown <= 0) {

		if (rate > 1.0f) {
		    rate -= RATE_INCR;
		    renderer.stop();
		    ((Clock)renderer).setRate(rate);
		    renderer.start();
		}

		overflown = FLOW_LIMIT / 2;
	    }
	}

	// Each buffer is broken down into smaller chunks for processing
	// as defined by chunkSize.
	//
	// EOM is trickier.  We don't want to send multiple EOM's to
	// the renderer for each of the smaller chunks.  So we catch
	// it and send it only on the last chunk.
	do {

	    // Check for the preset stop time.  Return if we are done.
	    if (stopTime > -1 && elapseTime.value >= stopTime) {
		if (prefetching)
		    donePrefetch();
		return PlugIn.INPUT_BUFFER_NOT_CONSUMED;
	    }

	    // If we are prerolling, there's no need to break the data
	    // into smaller chunks for processing.
	    if (remain <= chunkSize || prerolling) {
		if (isEOM) {
		    isEOM = false;
		    buffer.setEOM(true);
		}
		len = remain;
	    } else {
		if (buffer.isEOM()) {
		    isEOM = true;
		    buffer.setEOM(false);
		}
		len = chunkSize;
	    }

	    buffer.setLength(len);
	    buffer.setOffset(offset);

	    if (prerolling && !handlePreroll(buffer)) {
		offset += len;
		remain -= len;
		continue;
	    }

	    try {

		rc = renderer.process(buffer);

	    } catch (Throwable e) {
		Log.dumpStack(e);
		if (moduleListener != null)
		    moduleListener.internalErrorOccurred(this);
	    }

	    if ((rc & PlugIn.PLUGIN_TERMINATED) != 0) {
		failed = true;
		if (moduleListener != null)
		    moduleListener.pluginTerminated(this);
		return rc;
	    }

	    if ((rc & PlugIn.BUFFER_PROCESSED_FAILED) != 0) {
		buffer.setDiscard(true);
		if (prefetching)
		    donePrefetch();
		return rc;

	    } 

	    if ((rc & PlugIn.INPUT_BUFFER_NOT_CONSUMED) != 0) {
		// Check what's been processed so far.
		 len -= buffer.getLength();
	    }

	    offset += len;
	    remain -= len;

	    // If the module is prefetching, we'll need to check to see 
	    // if the device has been prefetched.
	    if (prefetching && 
		(!(renderer instanceof Prefetchable) || 
		 ((Prefetchable)renderer).isPrefetched())) {
		// If EOM happens prefetch, disable the EOM.
		// We'll get another EOM from the source module again.
		isEOM = false;
		buffer.setEOM(false);
		donePrefetch();
		break;
	    }

	    elapseTime.update(len, buffer.getTimeStamp(), buffer.getFormat());

	} while (remain > 0 && !resetted);

	// Re-enable the EOM flag if it were disabled previously.
	if (isEOM)
	    buffer.setEOM(true);

	buffer.setLength(remain);
	buffer.setOffset(offset);

	if (rc == PlugIn.BUFFER_PROCESSED_OK)
	    framesPlayed++;

	return rc;
    
protected booleanreinitRenderer(javax.media.Format input)
Attempt to re-initialize the renderer given a new input format.

	if (renderer != null) {
	    if (renderer.setInputFormat(input) != null) {
		// Fine, the existing renderer still works.
		return true;
	    }
	}

	if (started) {
	    renderer.stop();
	    renderer.reset();
	}

	renderer.close();
	renderer = null;

	Renderer r;
	if ((r = SimpleGraphBuilder.findRenderer(input)) == null)
	    return false;

	setRenderer(r);
	if (started)
	    renderer.start();

	chunkSize = computeChunkSize(input);

	return true;
    
public voidreset()

	super.reset();
	prefetching = false;
    
public voidresetFramesPlayed()

	framesPlayed = 0;
    
protected booleanscheduleBuffer(javax.media.Buffer buf)
Handed a buffer, this function does the scheduling of the buffer processing. It in turn calls processBuffer to do the real processing.


	int rc = PlugIn.BUFFER_PROCESSED_OK;

	Format format = buf.getFormat();

	if (format == null) {
	    // Something's weird, we'll just assume it's the previous
	    // format.
	    format = ic.getFormat();
	    buf.setFormat(format);
	}

	// Handle mid-stream format change.
	if (format != ic.getFormat() && !format.equals(ic.getFormat()) &&
	    !buf.isDiscard()) {
	    // Return if failed.
	    if (!handleFormatChange(format))
		return false;
	}

	// Signal the engine if the marker bit is set.
	if ((buf.getFlags() & Buffer.FLAG_SYSTEM_MARKER) != 0 &&
	    moduleListener != null) {
	    moduleListener.markedDataArrived(this, buf);
	    buf.setFlags(buf.getFlags() & ~Buffer.FLAG_SYSTEM_MARKER);
	}

	// Now on to scheduling!
	// Whether to do synchronization or not depends on the following
	// predicate.

	if (prefetching || 
	    (format instanceof AudioFormat) || 
	    buf.getTimeStamp() <= 0 ||
	    (buf.getFlags() & Buffer.FLAG_NO_SYNC) == Buffer.FLAG_NO_SYNC ||
	    noSync) {

	    // Handle non-scheduled data.
	    // Audio is handled here too since the data itself dictates
	    // the timing, not the time stamps.
	    // It also handles the prefetching cycle since there's no
	    // need to wait for presentation time.
	   /*
	    if (format instanceof javax.media.format.VideoFormat) {
		System.err.println("BRM: display on prefetch: " + 
					buf.getSequenceNumber());
	    }
	   */

	    if (!buf.isDiscard())
		rc = processBuffer(buf);

	} else  {

	    // Handle scheduled data.
	    // Video with a preset presentation timestamp is handled here.

	    long mt = getSyncTime(buf.getTimeStamp());

	    // Let's bring the #'s back to milli seconds range.
	    long lateBy = mt/1000000L - buf.getTimeStamp()/1000000L - 
				getLatency()/1000000L;

	    /*
	    System.err.println("VR: PT = " + buf.getTimeStamp()/1000000L + 
				" MT = " + mt/1000000L + 
				" lateBy = " + lateBy);
	    */

	    // Check the presentation schedule.

	    if (storedBuffer == null && lateBy > 0) {

		// It's behind schedule.
		//System.err.println("frame behind by: " + lateBy);

		if (buf.isDiscard()) {

		    // The upstream is telling me to discard this frame.
		    // This means that the upstream has drop a frame.
		    // So when the next frame comes, I'll remember not
		    // to drop a frame again.  Otherwise, we'll end up
		    // double-dropping frames.
		    notToDropNext = true;
		    //System.err.println("discard frame");

		} else {

		    if (buf.isEOM()) {

			// Don't drop the next (first frame).
			notToDropNext = true;

		    } else {

			// Report the frame behind time to the engine.
			if (moduleListener != null && 
			    format instanceof VideoFormat) {
			    float fb = lateBy * frameRate / 1000f;
			    if (fb < 1f)
				fb = 1f;
			    moduleListener.framesBehind(this, fb, ic);
			    framesWereBehind = true;
			    //System.err.println("frames behind = " + fb);
			}
		    }

		    if ((buf.getFlags() & Buffer.FLAG_NO_DROP) != 0) {

			// Process the frame if we are not allowed to drop 
			// the frame.

			rc = processBuffer(buf);

		    } else {

			// Do not give up too easily.  Allow for a few more
			// provisions before giving up on rendering the frame.

			if (lateBy < LEEWAY || notToDropNext ||
			    (buf.getTimeStamp() - lastRendered) > 1000000000L) {

			    rc = processBuffer(buf);
			    lastRendered = buf.getTimeStamp();
			    notToDropNext = false;
			} else {
			    //System.err.println("frame dropped");
			}
		    }
		}

	    } else {

		// It's either on time or ahead of schedule.

	        //System.err.println("VR: PT = " + 
		//	buf.getTimeStamp() + " MT = " + mt);

		//System.err.println("frame ahead by: " + lateBy);

		// Report "on time" to the engine.
		if (moduleListener != null && framesWereBehind &&
		    format instanceof VideoFormat) {
		    moduleListener.framesBehind(this, 0f, ic);
		    framesWereBehind = false;
		    //System.err.println("frames behind = 0");
		}

		if (!buf.isDiscard()) {
		    // Wait if we are ahead of the presentation time
		    // or the NO_WAIT flag is off.
		    if ((buf.getFlags() & Buffer.FLAG_NO_WAIT) == 0)
		        waitForPT(buf.getTimeStamp());
	     
		    if (!resetted) {
		        rc = processBuffer(buf);
			lastRendered = buf.getTimeStamp();
		    }
		}
	    }
	}

	// Check for processing return code.

	if ((rc & PlugIn.BUFFER_PROCESSED_FAILED) != 0) {
	    storedBuffer = null;
	} else if ((rc & PlugIn.INPUT_BUFFER_NOT_CONSUMED) != 0) {
	    // Save what's left for the next processing round.
	    // Do not return the buf back.
	    storedBuffer = buf;
	} else {
	    // Success.
	    storedBuffer = null;
	    if (buf.getDuration() >= 0)
		lastDuration = buf.getDuration();
	}

	return true;
    
public voidsetFormat(com.sun.media.Connector connector, javax.media.Format format)

	renderer.setInputFormat(format);
	if (format instanceof VideoFormat) {
	    float fr = ((VideoFormat)format).getFrameRate();
	    if (fr != VideoFormat.NOT_SPECIFIED)
		frameRate = fr;
	}
    
public voidsetPreroll(long wanted, long actual)
Enable prerolling.

	super.setPreroll(wanted, actual);
	elapseTime.setValue(actual);
    
protected voidsetRenderer(javax.media.Renderer r)

	renderer = r;
	if (renderer instanceof Clock)
	    setClock((Clock)renderer);
    
public voidtriggerReset()

	if (renderer != null)
	    renderer.reset();

	synchronized (prefetchSync) {
	    prefetching = false;
	    // If we are already done with the reset, there's no need
	    // to re-start the renderThread.  It's needed only if the
	    // data is blocked at the renderer when reset was called.
	    if (resetted)
		renderThread.start();
	}
    
private booleanwaitForPT(long pt)
If the presentation time has not been reached, this function will wait until that happens.


                        
        
	long mt = getSyncTime(pt);
	long aheadBy, lastAheadBy = -1;
	long interval;
	long before, slept;
	int beenHere = 0;

	aheadBy = (pt - mt)/1000000L;
	if (rate != 1.0f)
	    aheadBy = (long)((float)aheadBy / rate);

	while (aheadBy > systemErr && !resetted) {

	    if (aheadBy == lastAheadBy) {
		// Somehow, time hasn't changed at all since the last
		// time we slept (perhaps no audio samples updated),
		// we'll use a different scheme to compute the interval.

		// We'll compute the regular interval, plus an additional
		// 3 msecs every time we are here until we reach 33 msecs.
		interval = aheadBy + (5 * beenHere);
		if (interval > 33L)
		    interval = 33L;
		else
		    beenHere++;
		//System.err.println("been here: " + beenHere);
	    } else {
		interval = aheadBy;
		beenHere = 0;
	    }

	    // Don't sleep more than 1/8 sec.
	    // We'll wake up and check time again.
	    interval = (interval > 125L ? 125L : interval);

	    //System.err.println("mt = " + mt + " pt = " + pt);
	    //System.err.println("interval = " + interval);

	    before = System.currentTimeMillis();

	    // The interval is scheduled at ahead of time by the
	    // expected system error.
	    interval -= systemErr;

	    try {
		if (interval > 0)
		    Thread.currentThread().sleep(interval);
	    } catch (InterruptedException e) {}

	    slept = System.currentTimeMillis() - before;

	    // Compute the system err: the actual time slept minus the
	    // the desired sleep time.  Then take the average.
	    systemErr = (slept - interval + systemErr)/2;

	    // Rule out some illegal numbers.
	    if (systemErr < 0)
		systemErr = 0;
	    else if (systemErr > interval)
		systemErr = interval;

	    //System.err.println("slept = " + slept + " err = " + systemErr);

	    // Check the time again to see if we need to sleep more.
	    mt = getSyncTime(pt);

	    lastAheadBy = aheadBy;
    	    aheadBy = (pt - mt)/1000000L;
	    if (rate != 1.0f)
		aheadBy = (long)((float)aheadBy / rate);

	    if (getState() != Controller.Started)
		break;
	}
	return true;