Methods Summary |
---|
public void | abortPrefetch()
renderThread.pause();
renderer.close();
prefetching = false;
opened = false;
|
private int | computeChunkSize(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 void | doClose()
renderThread.kill();
if (renderer != null)
renderer.close();
if (rtpTimeBase != null) {
RTPTimeBase.remove(this, rtpCNAME);
rtpTimeBase = null;
}
|
public void | doDealloc()
renderer.close();
|
public void | doFailedPrefetch()
renderThread.pause();
renderer.close();
opened = false;
prefetching = false;
|
public boolean | doPrefetch()
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 boolean | doProcess()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 boolean | doRealize()
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 void | doStart()
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 void | doStop()
started = false;
prefetching = true;
super.doStop();
// Clocked renderer is handled by the super.doStop().
if (renderer != null && !(renderer instanceof Clock))
renderer.stop();
|
private void | donePrefetch()Handles the aftermath of prefetching.
synchronized (prefetchSync) {
if (!started && prefetching)
renderThread.pause();
prefetching = false;
}
if (moduleListener != null)
moduleListener.bufferPrefetched(this);
|
public void | doneReset()
renderThread.pause();
|
public java.lang.Object | getControl(java.lang.String s)
return renderer.getControl(s);
|
public java.lang.Object[] | getControls()
return renderer.getControls();
|
public int | getFramesPlayed()
return framesPlayed;
|
public long | getRTPTime()
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.Renderer | getRenderer()
return renderer;
|
private long | getSyncTime(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 boolean | handleFormatChange(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 boolean | handlePreroll(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 boolean | hasReachAudioPrerollTarget(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 boolean | isThreaded()
return true;
|
protected void | process()
|
public int | processBuffer(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 boolean | reinitRenderer(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 void | reset()
super.reset();
prefetching = false;
|
public void | resetFramesPlayed()
framesPlayed = 0;
|
protected boolean | scheduleBuffer(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 void | setFormat(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 void | setPreroll(long wanted, long actual)Enable prerolling.
super.setPreroll(wanted, actual);
elapseTime.setValue(actual);
|
protected void | setRenderer(javax.media.Renderer r)
renderer = r;
if (renderer instanceof Clock)
setClock((Clock)renderer);
|
public void | triggerReset()
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 boolean | waitForPT(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;
|