/* * @(#)BasicMuxModule.java 1.35 02/08/21 * * Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved. */ package com.sun.media; import javax.media.*; import javax.media.format.*; import javax.media.protocol.*; import com.sun.media.util.ElapseTime; /** * BasicMuxModule is a module which have InputConnectors and no * OutputConnectors. * It receives data from its inputs, feed that to a plugin Multiplexer * which then outputs the data via an output DataSource. **/ public class BasicMuxModule extends BasicSinkModule { protected Multiplexer multiplexer; protected Format inputs[]; protected InputConnector ics[]; protected boolean prefetchMarkers[]; protected boolean endMarkers[]; protected boolean resettedMarkers[]; protected boolean stopAtTimeMarkers[]; protected boolean paused[]; protected boolean prerollTrack[]; private Object pauseSync[]; protected ElapseTime elapseTime[]; protected boolean prefetching = false; protected boolean started = false; // state of the controller. private boolean closed = false; private boolean failed = false; private Object prefetchSync = new Object(); private float frameRate = 30f; private float lastFramesBehind = -1f; private int framesPlayed = 0; private VideoFormat rtpVideoFormat = null; private VideoFormat firstVideoFormat = null; public static String ConnectorNamePrefix = "input"; private long bitsWritten = 0; // For comparing formats. static AudioFormat mpegAudio = new AudioFormat(AudioFormat.MPEG_RTP); protected BasicMuxModule(Multiplexer m, Format inputs[]) { multiplexer = m; if (inputs != null) { InputConnector ic; ics = new InputConnector[inputs.length]; for (int i = 0; i < inputs.length; i++) { ic = new MyInputConnector(); ic.setSize(1); ic.setModule(this); registerInputConnector(ConnectorNamePrefix + i, ic); ics[i] = ic; if ( inputs[i] instanceof VideoFormat && firstVideoFormat == null) { firstVideoFormat = (VideoFormat) inputs[i]; String encoding = inputs[i].getEncoding().toUpperCase(); if (encoding.endsWith("RTP")) rtpVideoFormat = firstVideoFormat; } } this.inputs = inputs; } if (multiplexer != null && multiplexer instanceof Clock) setClock((Clock)multiplexer); setProtocol(Connector.ProtocolPush); } public boolean isThreaded() { return false; } public boolean doRealize() { if (multiplexer == null || inputs == null) return false; try { multiplexer.open(); } catch (ResourceUnavailableException e) { return false; } prefetchMarkers = new boolean[ics.length]; endMarkers = new boolean[ics.length]; resettedMarkers = new boolean[ics.length]; stopAtTimeMarkers = new boolean[ics.length]; paused = new boolean[ics.length]; prerollTrack = new boolean[ics.length]; pauseSync = new Object[ics.length]; elapseTime = new ElapseTime[ics.length]; for (int i = 0; i < ics.length; i++) { prerollTrack[i] = false; pauseSync[i] = new Object(); elapseTime[i] = new ElapseTime(); } pause(); return true; } public boolean doPrefetch() { if (! ((PlaybackEngine)controller).prefetchEnabled) return true; resetPrefetchMarkers(); prefetching = true; resume(); return true; } public void doFailedPrefetch() { prefetching = false; } public void abortPrefetch() { //multiplexer.close(); prefetching = false; } /** * Enable prerolling. */ public void setPreroll(long wanted, long actual) { super.setPreroll(wanted, actual); for (int i = 0; i < elapseTime.length; i++) { elapseTime[i].setValue(actual); // There's a bug in the MPEG packetizer that prevents prerolling // from working properly. The timestamps on the MPEG_RTP // buffers do not corresponds to the media time set after // a seek. So we're disabling prerolling for MPEG_RTP here. if (inputs[i] instanceof AudioFormat && mpegAudio.matches(inputs[i])) { prerollTrack[i] = false; } else prerollTrack[i] = true; } } public void doStart() { super.doStart(); resetEndMarkers(); resetStopAtTimeMarkers(); started = true; synchronized (prefetchSync) { prefetching = false; resume(); } } public void doStop() { super.doStop(); started = false; resetPrefetchMarkers(); prefetching = true; } public void doDealloc() { //multiplexer.close(); } public void doClose() { multiplexer.close(); closed = true; for (int i = 0; i < pauseSync.length; i++) { synchronized (pauseSync[i]) { pauseSync[i].notifyAll(); } } } /** * Internally, this pauses the processing thread from pushing more * data into the multiplexer. */ void pause() { for (int i = 0; i < paused.length; i++) paused[i] = true; } /** * Internally, this resumes the processing thread to push * data into the multiplexer. */ void resume() { for (int i = 0; i < pauseSync.length; i++) { synchronized (pauseSync[i]) { paused[i] = false; pauseSync[i].notifyAll(); } } } /** * This is the main processing function. It is called when * one of the the upstream modules pushes a buffer to this module. */ public void connectorPushed(InputConnector ic) { int idx = -1; // Determine track index. // Do some loop unrolling to find the track index since there's // probably just 2 tracks. if (ics[0] == ic) idx = 0; else if (ics[1] == ic) idx = 1; else { for (int i = 2; i < ics.length; i++ ) { if (ics[i] == ic) { idx = i; break; } } if (idx == -1) { // Something is terribly wrong. throw new RuntimeException("BasicMuxModule: unmatched input connector!"); } } // This weird looking while loop here is necessary. // What we are trying to achieve here is to never return from // connectorPush until we actually finish processing the buffer. // If a preset stop time is reached, we'll keep looping here. // Without this loop, we'll miss one valid buffer from the // upstream module. This is indeed very tricky. while (true) { if (paused[idx]) { // Not sure how efficient the synchronized block is. // So I'm doing the check before entering into it. // Another check is performed inside the block. synchronized (pauseSync[idx]) { try { while (paused[idx] && !closed) pauseSync[idx].wait(); } catch (Exception e) {} } } // Check to see if we have reached the preset stop time. // If so, we'll notify the player and then loop back to // the pause above. That way, we won't go on to process // the data. if (stopTime > -1 && elapseTime[idx].value >= stopTime) { paused[idx] = true; if (checkStopAtTime(idx)) { if (multiplexer instanceof Drainable) ((Drainable)multiplexer).drain(); doStop(); if (moduleListener != null) moduleListener.stopAtTime(this); } } else break; // We've checked the stop time and we can just // move on. } Buffer buffer = ic.getValidBuffer(); int flags = buffer.getFlags(); int rc = 0; // Check if we are in the resetted state. if (resetted) { // Check if the input buffer contains the zero-length // flush flag. if ((flags & Buffer.FLAG_FLUSH) != 0) { // This causes a deadlock interacting with the sync mux. // Pause the particular track. //paused[idx] = true; // If all tracks are resetted, then we are done. if (checkResetted(idx)) { resetted = false; doStop(); if (moduleListener != null) moduleListener.resetted(this); } } // In the resetted state, we'll not pass any data to the // multiplexer. ic.readReport(); return; } if (failed || closed || buffer.isDiscard()) { ic.readReport(); return; } if (PlaybackEngine.DEBUG) jmd.moduleIn(this, 0, buffer, true); // Signal the engine if the marker bit is set. if ((flags & Buffer.FLAG_SYSTEM_MARKER) != 0 && moduleListener != null) { moduleListener.markedDataArrived(this, buffer); flags = flags & ~Buffer.FLAG_SYSTEM_MARKER; buffer.setFlags(flags ); } // Flag to indicate if data is prerolled, then we don't // need to process it any further. boolean dataPrerolled = false; Format format = buffer.getFormat(); if (format == null) { // Something's weird, we'll just assume it's the previous // format. format = ic.getFormat(); buffer.setFormat(format); } // Update the elapse time for prerolling and checking the // preset stop time. if (elapseTime[idx].update(buffer.getLength(), buffer.getTimeStamp(), format)) { // If an elapse time can be computed. // Check prerolling. if (prerollTrack[idx]) { long target = getMediaNanoseconds(); if (elapseTime[idx].value > target) { // Done with prerolling. if (format instanceof AudioFormat && AudioFormat.LINEAR.equals(format.getEncoding())) { int remain = (int)ElapseTime.audioTimeToLen( elapseTime[idx].value - target, (AudioFormat)format); int offset = buffer.getOffset() + buffer.getLength() - remain; if (offset >= 0) { buffer.setOffset(offset); buffer.setLength(remain); } } prerollTrack[idx] = false; elapseTime[idx].setValue(target); } else { dataPrerolled = true; } } // Check the preset stop time. if (stopTime > -1 && elapseTime[idx].value > stopTime && format instanceof AudioFormat) { // Processing the full chunk will have exceeded the // preset stop time. We'll cut the audio data. long exceeded = elapseTime[idx].value - stopTime; int exceededLen = (int)ElapseTime.audioTimeToLen(exceeded, (AudioFormat)format); if (buffer.getLength() > exceededLen) buffer.setLength(buffer.getLength() - exceededLen); } } // Report the frame behind time for the engine. if (moduleListener != null && format instanceof VideoFormat) { // Check to see if the frame is delayed. long mt = getMediaNanoseconds(); // Let's bring the #'s back to milli seconds range. long lateBy = mt/1000000L - buffer.getTimeStamp()/1000000L - getLatency()/1000000L; //System.err.println("lateBy = " + lateBy); float fb = lateBy * frameRate / 1000f; if (fb < 0) fb = 0; if (lastFramesBehind != fb && (flags & Buffer.FLAG_NO_DROP) == 0) { moduleListener.framesBehind(this, fb, ic); lastFramesBehind = fb; } //System.err.println("frames behind = " + fb); } do { if (!dataPrerolled) { try { rc = multiplexer.process(buffer, idx); } catch (Throwable e) { Log.dumpStack(e); if (moduleListener != null) moduleListener.internalErrorOccurred(this); } // Update the frame rate if ( rc == PlugIn.BUFFER_PROCESSED_OK && format == firstVideoFormat) { if (format == rtpVideoFormat) { if ((flags & Buffer.FLAG_RTP_MARKER) > 0) framesPlayed++; } else { framesPlayed++; } } } else { rc = PlugIn.BUFFER_PROCESSED_OK; } if ((rc & PlugIn.PLUGIN_TERMINATED) != 0) { failed = true; if (moduleListener != null) moduleListener.pluginTerminated(this); ic.readReport(); return; } // If the module is prefetching, we'll need to check to see // if the device has been prefetched. if (prefetching && (!(multiplexer instanceof Prefetchable) || ((Prefetchable)multiplexer).isPrefetched())) { synchronized (prefetchSync) { if (!started && prefetching && !resetted) paused[idx] = true; if (checkPrefetch(idx)) prefetching = false; } // Notify the engine prefetching is done. if (!prefetching && moduleListener != null) moduleListener.bufferPrefetched(this); } } while (!resetted && rc == PlugIn.INPUT_BUFFER_NOT_CONSUMED); bitsWritten += buffer.getLength(); if (buffer.isEOM()) { if (!resetted) paused[idx] = true; if (checkEnd(idx)) { doStop(); if (moduleListener != null) moduleListener.mediaEnded(this); } } ic.readReport(); if (PlaybackEngine.DEBUG) jmd.moduleIn(this, 0, buffer, false); } void resetPrefetchMarkers() { synchronized (prefetchMarkers) { for (int i = 0; i < prefetchMarkers.length; i++) prefetchMarkers[i] = false; } } boolean checkPrefetch(int idx) { synchronized (prefetchMarkers) { prefetchMarkers[idx] = true; for (int i = 0; i < prefetchMarkers.length; i++) { if (!prefetchMarkers[i]) return false; } return true; } } void resetEndMarkers() { synchronized (endMarkers) { for (int i = 0; i < endMarkers.length; i++) endMarkers[i] = false; } } boolean checkEnd(int idx) { synchronized (endMarkers) { endMarkers[idx] = true; for (int i = 0; i < endMarkers.length; i++) { if (!endMarkers[i]) return false; } return true; } } void resetResettedMarkers() { synchronized (resettedMarkers) { for (int i = 0; i < resettedMarkers.length; i++) resettedMarkers[i] = false; } } boolean checkResetted(int idx) { synchronized (resettedMarkers) { resettedMarkers[idx] = true; for (int i = 0; i < resettedMarkers.length; i++) { if (!resettedMarkers[i]) return false; } return true; } } void resetStopAtTimeMarkers() { synchronized (stopAtTimeMarkers) { for (int i = 0; i < stopAtTimeMarkers.length; i++) stopAtTimeMarkers[i] = false; } } boolean checkStopAtTime(int idx) { synchronized (stopAtTimeMarkers) { stopAtTimeMarkers[idx] = true; for (int i = 0; i < stopAtTimeMarkers.length; i++) { if (!stopAtTimeMarkers[i]) return false; } return true; } } // Not applicable. protected void process() {} public void reset() { super.reset(); resetResettedMarkers(); prefetching = false; } public void triggerReset() { multiplexer.reset(); synchronized (prefetchSync) { prefetching = false; if (resetted) resume(); } } public DataSource getDataOutput() { return multiplexer.getDataOutput(); } public Multiplexer getMultiplexer() { return multiplexer; } public Object[] getControls() { return multiplexer.getControls(); } public Object getControl(String s) { return multiplexer.getControl(s); } public void setFormat(Connector connector, Format format) { if (format instanceof VideoFormat) { float fr = ((VideoFormat)format).getFrameRate(); if (fr != VideoFormat.NOT_SPECIFIED) frameRate = fr; } } public int getFramesPlayed() { return framesPlayed; } public void resetFramesPlayed() { framesPlayed = 0; } public long getBitsWritten() { return bitsWritten; } public void resetBitsWritten() { bitsWritten = 0; } class MyInputConnector extends BasicInputConnector { public MyInputConnector() { } public String toString() { return super.toString() + ": " + getFormat(); } } }