All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.fmj.media.PlaybackEngine Maven / Gradle / Ivy

There is a newer version: 1.0.2-jitsi
Show newest version
package net.sf.fmj.media;

import java.awt.*;
import java.io.*;
import java.util.*;

import javax.media.*;
import javax.media.control.*;
import javax.media.format.*;
import javax.media.protocol.*;
import javax.media.renderer.*;

import net.sf.fmj.filtergraph.*;
import net.sf.fmj.media.control.*;
import net.sf.fmj.media.protocol.*;
import net.sf.fmj.media.renderer.audio.*;
import net.sf.fmj.media.util.*;

import com.sun.media.controls.*;

/**
 * PlaybackEngine implements the media engine for playback.
 */
public class PlaybackEngine extends BasicController implements ModuleListener
{
    class BitRateA extends BitRateAdapter implements Owned
    {
        public BitRateA(int initialBitRate, int minBitRate, int maxBitRate,
                boolean settable)
        {
            super(initialBitRate, minBitRate, maxBitRate, settable);
        }

        @Override
        public Component getControlComponent()
        {
            return null;
        }

        public Object getOwner()
        {
            return player;
        }

        @Override
        public int setBitRate(int rate)
        {
            this.value = rate;
            return this.value;
        }
    }

    class HeavyPanel extends java.awt.Panel implements VisualContainer
    {
        public HeavyPanel(Vector visuals)
        {
        }
    }

    class LightPanel extends java.awt.Container implements VisualContainer
    {
        public LightPanel(Vector visuals)
        {
        }
    }

    /**
     * This is the Graph builder extended from SimpleGraphBuilder to generate
     * the data flow graph for the given track.
     */
    class PlayerGraphBuilder extends SimpleGraphBuilder
    {
        protected PlaybackEngine engine;

        PlayerGraphBuilder(PlaybackEngine engine)
        {
            this.engine = engine;
        }

        @Override
        protected GraphNode buildTrackFromGraph(BasicTrackControl tc,
                GraphNode node)
        {
            return engine.buildTrackFromGraph(tc, node);
        }
    }

    /**
     * Track Control.
     */
    class PlayerTControl extends BasicTrackControl implements Owned
    {
        protected PlayerGraphBuilder gb;

        public PlayerTControl(PlaybackEngine engine, Track track,
                OutputConnector oc)
        {
            super(engine, track, oc);
        }

        /**
         * Top level routine to build a single track.
         */
        @Override
        public boolean buildTrack(int trackID, int numTracks)
        {
            if (gb == null)
                gb = new PlayerGraphBuilder(engine);
            else
                gb.reset();
            boolean rtn = gb.buildGraph(this);

            // dispose the old GraphBuilder after building a track.
            // The cache is not valid anymore.
            gb = null;

            return rtn;
        }

        @Override
        protected FrameRateControl frameRateControl()
        {
            return frameRateControl;
        }

        public Object getOwner()
        {
            return player;
        }

        /**
         * Returns true if this track holds the master time base.
         */
        @Override
        public boolean isTimeBase()
        {
            for (int j = 0; j < this.modules.size(); j++)
            {
                if (this.modules.elementAt(j) == masterSink)
                    return true;
            }
            return false;
        }

        @Override
        protected ProgressControl progressControl()
        {
            return progressControl;
        }
    }

    class SlaveClock implements Clock
    {
        Clock master, current;
        BasicClock backup;

        SlaveClock()
        {
            backup = new BasicClock();
            current = backup;
        }

        public long getMediaNanoseconds()
        {
            return current.getMediaNanoseconds();
        }

        public Time getMediaTime()
        {
            return current.getMediaTime();
        }

        public float getRate()
        {
            return current.getRate();
        }

        public Time getStopTime()
        {
            return backup.getStopTime();
        }

        public Time getSyncTime()
        {
            return current.getSyncTime();
        }

        public TimeBase getTimeBase()
        {
            return current.getTimeBase();
        }

        public Time mapToTimeBase(Time t) throws ClockStoppedException
        {
            return current.mapToTimeBase(t);
        }

        protected void reset(boolean useMaster)
        {
            if (master != null && useMaster)
                current = master;
            else
            {
                // Align the backup clock's media time with the
                // master's time.
                if (master != null)
                {
                    synchronized (backup)
                    {
                        boolean started = false;
                        if (backup.getState() == BasicClock.STARTED)
                        {
                            backup.stop();
                            started = true;
                        }
                        backup.setMediaTime(master.getMediaTime());
                        if (started)
                            backup.syncStart(backup.getTimeBase().getTime());
                    }
                }
                current = backup;
            }
        }

        public void setMaster(Clock master)
        {
            this.master = master;
            current = (master == null ? backup : master);
            if (master != null)
            {
                try
                {
                    backup.setTimeBase(master.getTimeBase());
                } catch (IncompatibleTimeBaseException e)
                {
                }
            }
        }

        public void setMediaTime(Time now)
        {
            synchronized (backup)
            {
                if (backup.getState() == BasicClock.STARTED)
                {
                    backup.stop();
                    backup.setMediaTime(now);
                    backup.syncStart(backup.getTimeBase().getTime());
                } else
                    backup.setMediaTime(now);
            }
        }

        public float setRate(float factor)
        {
            return backup.setRate(factor);
        }

        public void setStopTime(Time t)
        {
            synchronized (backup)
            {
                backup.setStopTime(t);
            }
        }

        public void setTimeBase(TimeBase tb)
                throws IncompatibleTimeBaseException
        {
            synchronized (backup)
            {
                backup.setTimeBase(tb);
            }
        }

        public void stop()
        {
            synchronized (backup)
            {
                backup.stop();
            }
        }

        public void syncStart(Time tbt)
        {
            synchronized (backup)
            {
                if (backup.getState() != BasicClock.STARTED)
                    backup.syncStart(tbt);
            }
        }
    }

    protected BasicPlayer player;
    protected DataSource dsource;
    protected Vector modules;
    protected Vector filters;
    protected Vector sinks;
    protected Vector waitPrefetched;
    protected Vector waitStopped;
    protected Vector waitEnded;

    protected Vector waitResetted;
    protected Track tracks[];
    protected Demultiplexer parser;
    protected BasicSinkModule masterSink = null;
    protected BasicSourceModule source;
    protected SlaveClock slaveClock;
    private boolean internalErrorOccurred = false;
    protected boolean prefetched = false;

    protected boolean started = false;
    private boolean dataPathBlocked = false;
    private boolean useMoreRenderBuffer = false;
    private boolean deallocated = false;
    public boolean prefetchEnabled = true;
    static protected boolean needSavingDB = false;

    private Time timeBeforeAbortPrefetch = null;

    private float rate = 1.0f;

    protected BitRateControl bitRateControl;
    protected FrameRateControl frameRateControl;

    protected FramePositioningControl framePositioningControl = null;
    private long latency = 0;

    protected Container container = null;
    static public boolean TRACE_ON = false;
    // Controls.
    protected BasicTrackControl trackControls[] = new BasicTrackControl[0];

    protected ProgressControl progressControl;

    private long realizeTime;

    private long prefetchTime;

    // Error messages.
    static String NOT_CONFIGURED_ERROR = "cannot be called before configured";

    static String NOT_REALIZED_ERROR = "cannot be called before realized";
    static String STARTED_ERROR = "cannot be called after started";

    /**
     * Turn on memory trace to assit debugging.
     */
    static public void setMemoryTrace(boolean on)
    {
        TRACE_ON = on;
    }

    String configError = "Failed to configure: " + this;

    String configIntError = "  The configure process is being interrupted.\n";

    String configInt2Error = "interrupted while the Processor is being configured.";

    String parseError = "failed to parse the input media.";

    protected String realizeError = "Failed to realize: " + this;
    protected String timeBaseError = "  Cannot manage the different time bases.\n";
    protected String genericProcessorError = "cannot handle the customized options set on the Processor.\nCheck the logs for full details.";

    String prefetchError = "Failed to prefetch: " + this;

    RTPInfo rtpInfo = null;

    boolean testedRTP = false;

    boolean prefetchLogged = false;

    long markedDataStartTime = 0;

    boolean reportOnce = false;

    long lastBitRate = 0;

    long lastStatsTime = 0; // so now - lastStatsTime will not be 0.

    static boolean USE_MASTER = true;

    static boolean USE_BACKUP = false;

    /**
     * Return true if the given format is a raw video format.
     */
    static boolean isRawVideo(Format fmt)
    {
        return (fmt instanceof RGBFormat || fmt instanceof YUVFormat);
    }

    static void profile(String msg, long time)
    {
        Log.profile("Profile: " + msg + ": "
                + (System.currentTimeMillis() - time) + " ms\n");
    }

    public PlaybackEngine(BasicPlayer p)
    {
        long initTime = System.currentTimeMillis();

        player = p;
        createProgressControl();

        setClock(slaveClock = new SlaveClock());
        stopThreadEnabled = false;

        profile("instantiation", initTime);
    }

    /**
     * Called when doConfigure() is aborted.
     */
    @Override
    protected synchronized void abortConfigure()
    {
        // The parser could be in the middle of realizing. We'll need to
        // abort it.
        if (source != null)
            source.abortRealize();
    }

    /**
     * Called when the prefetch() is aborted, i.e. deallocate() was called while
     * prefetching. Release all resources claimed previously by the prefetch
     * call. Override this to implement subclass behavior.
     */
    @Override
    protected synchronized void abortPrefetch()
    {
        // Mark the time before the prefetch. At the next prefetch,
        // we'll sync up the media with this time.
        timeBeforeAbortPrefetch = getMediaTime();
        doReset();
        StateTransistor m;
        int size = modules.size();
        for (int i = 0; i < size; i++)
        {
            m = (StateTransistor) modules.elementAt(i);
            m.abortPrefetch();
        }
        deallocated = true;
    }

    /**
     * Called when the realize() is aborted, i.e. deallocate() was called while
     * realizing. Release all resources claimed previously by the realize()
     * call.
     */
    @Override
    protected synchronized void abortRealize()
    {
        StateTransistor m;
        int size = modules.size();
        for (int i = 0; i < size; i++)
        {
            m = (StateTransistor) modules.elementAt(i);
            m.abortRealize();
        }
    }

    /**
     * Return true if audio is present.
     */
    public boolean audioEnabled()
    {
        for (int i = 0; i < trackControls.length; i++)
        {
            if (trackControls[i].isEnabled()
                    && trackControls[i].getOriginalFormat() instanceof AudioFormat)
                return true;
        }
        return false;
    }

    public void bufferPrefetched(Module src)
    {
        if (!prefetchEnabled)
            return;
        if (src instanceof BasicSinkModule)
        {
            synchronized (waitPrefetched)
            {
                if (waitPrefetched.contains(src))
                    waitPrefetched.removeElement(src);
                if (waitPrefetched.isEmpty())
                {
                    // All sinks are prefetched. Wake up the
                    // doPrefetch() wait.
                    waitPrefetched.notifyAll();

                    if (!prefetchLogged)
                    {
                        profile("prefetch", prefetchTime);
                        prefetchLogged = true;
                    }

                    if (getState() != Controller.Started
                            && getTargetState() != Controller.Started)
                    {
                        // Non-blocking stop.
                        source.pause();
                    }
                    prefetched = true;
                }
            }
        }
    }

    /**
     * Construct a track (connected modules) from the specified node graph.
     * Return a non-null GraphNode if the track cannot be built. The non-null
     * node returned is the node that failed to be opened.
     */
    protected GraphNode buildTrackFromGraph(BasicTrackControl tc, GraphNode node)
    {
        BasicModule src = null, dst = null;
        InputConnector ic = null;
        OutputConnector oc = null;
        boolean lastNode = true;
        Vector used = new Vector(5);
        int indent = 0;

        if (node.plugin == null)
        {
            // There's nothing to build. I.e. the output from the source (demux)
            // works just fine. Probably just need to be multiplexed.
            return null;
        }

        Log.setIndent(indent++);

        // Build the graph from the last node.
        while (node != null && node.plugin != null)
        {
            if ((src = createModule(node, used)) == null)
            {
                // something terrible went wrong.
                Log.error("Internal error: buildTrackFromGraph");
                node.failed = true;
                return node;
            }

            // Mark the renderer module or the last output connector
            // from in the trackcontrol.
            if (lastNode)
            {
                if (src instanceof BasicRendererModule)
                {
                    tc.rendererModule = (BasicRendererModule) src;

                    // If we are dealing with an audio renderer here, we'll
                    // set the jitter buffer size.
                    if (useMoreRenderBuffer
                            && tc.rendererModule.getRenderer() instanceof AudioRenderer)
                        setRenderBufferSize(tc.rendererModule.getRenderer());

                } else if (src instanceof BasicFilterModule)
                {
                    tc.lastOC = src.getOutputConnector(null);
                    tc.lastOC.setFormat(node.output);
                }
                lastNode = false;
            }

            ic = src.getInputConnector(null);
            ic.setFormat(node.input);

            if (dst != null)
            {
                oc = src.getOutputConnector(null);
                ic = dst.getInputConnector(null);
                oc.setFormat(ic.getFormat());
            }

            src.setController(this);
            if (!src.doRealize())
            {
                // Log.write("Failed to open plugin: " + node.plugin);
                // Log.write("  with input: " + node.input);
                Log.setIndent(indent--);
                node.failed = true;
                return node;
            }

            if (oc != null && ic != null)
                connectModules(oc, ic, dst);

            dst = src;
            node = node.prev;
        }

        // All successful, so we need to add each modules under the engine's
        // management.

        dst = src;
        do
        {
            dst.setModuleListener(this);
            modules.addElement(dst);
            tc.modules.addElement(dst);
            if (dst instanceof BasicFilterModule)
                filters.addElement(dst);
            else if (dst instanceof BasicSinkModule)
                sinks.addElement(dst);
            oc = dst.getOutputConnector(null);
            if (oc == null || (ic = oc.getInputConnector()) == null
                    || (dst = (BasicModule) ic.getModule()) == null)
                break;
        }
        while (true);

        // Set the first output connector.
        tc.firstOC.setFormat(tc.getOriginalFormat());

        // Check if the first module has the format set.
        ic = src.getInputConnector(null);
        Format fmt = ic.getFormat();
        if (fmt == null || !fmt.equals(tc.getOriginalFormat()))
            ic.setFormat(tc.getOriginalFormat());

        connectModules(tc.firstOC, ic, src);

        Log.setIndent(indent--);

        return null;
    }

    /**
     * Connect the two given modules.
     */
    protected void connectModules(OutputConnector oc, InputConnector ic,
            BasicModule dst)
    {
        // If the dst is the renderer, adopt the dest's protocol.
        // Otherwise, adopt the source's protocol.
        if (dst instanceof BasicRendererModule)
            oc.setProtocol(ic.getProtocol());
        else
            ic.setProtocol(oc.getProtocol());

        oc.connectTo(ic, ic.getFormat());
    }

    /**
     * Create a realized filter module given the plugIn codec.
     */
    protected BasicModule createModule(GraphNode n, Vector used)
    {
        PlugIn p;
        BasicModule m = null;

        if (n.plugin == null)
            return null;

        if (used.contains(n.plugin))
        {
            // That plugin has already been used in the same path,
            // we'll need to instantiate another one of its kind.
            if (n.cname == null
                    || (p = SimpleGraphBuilder.createPlugIn(n.cname, -1)) == null)
            {
                Log.write("Failed to instantiate " + n.cname);
                return null;
            }
        } else
        {
            p = n.plugin;
            used.addElement(p);
        }

        if ((n.type == -1 || n.type == PlugInManager.RENDERER)
                && p instanceof Renderer)
        {
            m = new BasicRendererModule((Renderer) p);
        } else if ((n.type == -1 || n.type == PlugInManager.CODEC)
                && p instanceof Codec)
        {
            m = new BasicFilterModule((Codec) p);
        }

        return m;
    }

    /**
     * Create the progress status control.
     */
    public void createProgressControl()
    {
        // Build the progress control.
        StringControl frameRate;
        StringControl bitRate;
        StringControl videoProps;
        StringControl audioProps;
        StringControl videoCodec;
        StringControl audioCodec;

        frameRate = new StringControlAdapter();
        frameRate.setValue(" N/A");
        bitRate = new StringControlAdapter();
        bitRate.setValue(" N/A");
        videoProps = new StringControlAdapter();
        videoProps.setValue(" N/A");
        audioProps = new StringControlAdapter();
        audioProps.setValue(" N/A");
        audioCodec = new StringControlAdapter();
        audioCodec.setValue(" N/A");
        videoCodec = new StringControlAdapter();
        videoCodec.setValue(" N/A");
        progressControl = new ProgressControlAdapter(frameRate, bitRate,
                videoProps, audioProps, videoCodec, audioCodec);
    }

    protected Component createVisualContainer(Vector visuals)
    {
        Boolean hint = (Boolean) Manager.getHint(Manager.LIGHTWEIGHT_RENDERER);
        if (container == null)
        {
            if (hint == null || hint.booleanValue() == false)
            {
                container = new HeavyPanel(visuals);
            } else
            {
                container = new LightPanel(visuals);
            }

            container.setLayout(new FlowLayout());
            container.setBackground(Color.black);
            for (int i = 0; i < visuals.size(); i++)
            {
                Component c = (Component) visuals.elementAt(i);
                container.add(c);
                c.setSize(c.getPreferredSize());
            }
        }
        return container;
    }

    public void dataBlocked(Module src, boolean blocked)
    {
        dataPathBlocked = blocked;
        if (blocked)
        {
            // Break the lock for the blocking prefetch & reset.
            resetPrefetchedList();
            resetResettedList();
        }

        if (getTargetState() != Controller.Started)
        {
            return;
        }

        if (blocked)
        {
            localStop();
            setTargetState(Controller.Started); // NOTE: may cause race
                                                // condition
            sendEvent(new RestartingEvent(this, Controller.Started,
                    Controller.Prefetching, Controller.Started, getMediaTime()));
        } else
        {
            sendEvent(new StartEvent(this, Controller.Prefetched,
                    Controller.Started, Controller.Started, getMediaTime(),
                    getTimeBase().getTime()));
        }
    }

    /**
     * Invoked by close() to cleanup the Controller. Override this to implement
     * subclass behavior.
     */
    @Override
    protected synchronized void doClose()
    {
        if (modules == null)
        {
            if (source != null)
                source.doClose();
            return;
        }
        if (getState() == Started)
            localStop();
        if (getState() == Prefetched)
            doReset();
        StateTransistor m;
        int size = modules.size();
        for (int i = 0; i < size; i++)
        {
            m = (StateTransistor) modules.elementAt(i);
            m.doClose();
        }

        if (needSavingDB)
        {
            Resource.saveDB();
            needSavingDB = false;
        }
    }

    /**
     * Configuring the engine.
     */
    @Override
    protected boolean doConfigure()
    {
        if (!doConfigure1())
            return false;

        // The indices to the connector names, tracks, and track controls
        // should all correspond to each other.
        String names[] = source.getOutputConnectorNames();
        trackControls = new BasicTrackControl[tracks.length];
        for (int i = 0; i < tracks.length; i++)
        {
            trackControls[i] = new PlayerTControl(this, tracks[i],
                    source.getOutputConnector(names[i]));
        }

        return doConfigure2();
    }

    /**
     * Configure - Part I.
     */
    protected boolean doConfigure1()
    {
        long parsingTime = System.currentTimeMillis();

        modules = new Vector();
        filters = new Vector();
        sinks = new Vector();
        waitPrefetched = new Vector();
        waitStopped = new Vector();
        waitEnded = new Vector();
        waitResetted = new Vector();

        source.setModuleListener(this);
        source.setController(this);
        modules.addElement(source);

        // Realize the source.
        if (!source.doRealize())
        {
            Log.error(configError);
            if (source.errMsg != null)
                Log.error("  " + source.errMsg + "\n");
            player.processError = parseError;
            return false;
        }

        // Check for interrupt after a critical section.
        if (isInterrupted())
        {
            Log.error(configError);
            Log.error(configIntError);
            player.processError = configInt2Error;
            return false;
        }

        if ((parser = source.getDemultiplexer()) == null)
        {
            Log.error(configError);
            Log.error("  Cannot obtain demultiplexer for the source.\n");
            player.processError = parseError;
            return false;
        }

        // Build the track controls.
        try
        {
            tracks = parser.getTracks();
        } catch (Exception e)
        {
            Log.error(configError);
            Log.error("  Cannot obtain tracks from the demultiplexer: " + e
                    + "\n");
            player.processError = parseError;
            return false;
        }

        // Check for interrupt after a critical section.
        if (isInterrupted())
        {
            Log.error(configError);
            Log.error(configIntError);
            player.processError = configInt2Error;
            return false;
        }

        profile("parsing", parsingTime);

        return true;
    }

    /**
     * Configure - Part II.
     */
    protected boolean doConfigure2()
    {
        if (parser.isPositionable() && parser.isRandomAccess())
        {
            Track master = FramePositioningAdapter.getMasterTrack(tracks);
            if (master != null)
            {
                framePositioningControl = new FramePositioningAdapter(player,
                        master);
            }
        }

        return true;
    }

    /**
     * Called by deallocate(). Subclasses should implement this for its specific
     * behavior.
     */
    @Override
    protected void doDeallocate()
    {
    }

    /**
     * Called when the prefetch() has failed.
     */
    @Override
    protected synchronized void doFailedPrefetch()
    {
        StateTransistor m;
        int size = modules.size();
        for (int i = 0; i < size; i++)
        {
            m = (StateTransistor) modules.elementAt(i);
            m.doFailedPrefetch();
        }
        super.doFailedPrefetch();
    }

    /**
     * Called when realize() has failed.
     */
    @Override
    protected synchronized void doFailedRealize()
    {
        StateTransistor m;
        int size = modules.size();
        for (int i = 0; i < size; i++)
        {
            m = (StateTransistor) modules.elementAt(i);
            m.doFailedRealize();
        }
        super.doFailedRealize();
    }

    /**
     * The stub function to perform the steps to prefetch the controller.
     *
     * @return true if successful.
     */
    @Override
    protected synchronized boolean doPrefetch()
    {
        if (prefetched)
            return true;

        return doPrefetch1() && doPrefetch2();
    }

    /**
     * doPrefetch - Part I
     */
    protected boolean doPrefetch1()
    {
        // If the engine was deallocated before this prefetch, we'll
        // need to sync up the media at the time when the prefetch
        // happens.
        if (timeBeforeAbortPrefetch != null)
        {
            doSetMediaTime(timeBeforeAbortPrefetch);
            timeBeforeAbortPrefetch = null;
        }

        prefetchTime = System.currentTimeMillis();

        // Then prefetch the data path by starting the source
        // module without starting the renderer modules.
        resetPrefetchedList();

        // First prefetch each modules.

        // Fail if source cannot be prefetched.
        if (!source.doPrefetch())
        {
            Log.error(prefetchError);
            if (dsource != null)
                Log.error("  Cannot prefetch the source: "
                        + dsource.getLocator() + "\n");
            return false;
        }

        // Prefetch each track.
        boolean atLeastOneTrack = false;
        boolean usedToFailed;
        for (int i = 0; i < trackControls.length; i++)
        {
            usedToFailed = trackControls[i].prefetchFailed;

            // If this track used to fail and we haven't been
            // deallocated, then we just skip the track.
            if (usedToFailed && getState() > Prefetching)
                continue;

            if (trackControls[i].prefetchTrack())
            {
                atLeastOneTrack = true;
                if (usedToFailed)
                {
                    if (!manageTimeBases())
                    {
                        Log.error(prefetchError);
                        Log.error(timeBaseError);
                        return false;
                    }
                    // Sync up all the tracks with the current media time
                    // since a new track is added.
                    doSetMediaTime(getMediaTime());
                }

            } else
            {
                trackControls[i].prError();

                // If the failed track contain the time base,
                // we'll need to choose a new time base.
                if (trackControls[i].isTimeBase())
                {
                    // Fails if no new time base can be found.
                    // This should be rare.
                    if (!manageTimeBases())
                    {
                        Log.error(prefetchError);
                        Log.error(timeBaseError);
                        player.processError = timeBaseError;
                        return false;
                    }
                }

                if (trackControls[i].getFormat() instanceof AudioFormat
                        && trackControls[i].rendererFailed)
                {
                    player.processError = "cannot open the audio device.";
                }
            }
        }

        // Fail if not even one track can be prefetched.
        if (!atLeastOneTrack)
        {
            Log.error(prefetchError);
            return false;
        }

        player.processError = null;

        return true;
    }

    /**
     * doPrefetch - Part II
     */
    protected boolean doPrefetch2()
    {
        if (prefetchEnabled)
        {
            // Wait for notification from the renderer modules with the
            // bufferPrefetched event.
            synchronized (waitPrefetched)
            {
                source.doStart();
                try
                {
                    if (!waitPrefetched.isEmpty())
                    {
                        // Block for at most 3 sec. in case anything goes wrong
                        // at the renderer modules.
                        waitPrefetched.wait(3000);
                    }
                } catch (InterruptedException e)
                {
                }
            }

        } else
            prefetched = true;

        deallocated = false;

        return true;
    }

    /**
     * @return true if successful.
     */
    @Override
    protected synchronized boolean doRealize()
    {
        return doRealize1() && doRealize2();
    }

    /**
     * doRealize Part I
     */
    protected boolean doRealize1()
    {
        Log.comment("Building flow graph for: " + dsource.getLocator() + "\n");

        realizeTime = System.currentTimeMillis();

        boolean atLeastOneTrack = false;
        int trackID = 0;
        int numTracks = getNumTracks();

        // Go thru each track to build the graph.
        for (int i = 0; i < trackControls.length; i++)
        {
            // Skip the track if its disabled
            if (!trackControls[i].isEnabled())
                continue;

            Log.setIndent(0);
            Log.comment("Building Track: " + i);

            if (trackControls[i].buildTrack(trackID, numTracks))
            {
                // We've completed at least one track now.
                atLeastOneTrack = true;
                trackControls[i].setEnabled(true);
            } else if (trackControls[i].isCustomized())
            {
                // If the track is being customized and we cannot
                // build a graph for that track, we won't attempt to
                // build the rest of the tracks.
                Log.error(realizeError);
                trackControls[i].prError();
                player.processError = genericProcessorError;
                return false;
            } else
            {
                // Disable that track
                trackControls[i].setEnabled(false);
                Log.warning("Failed to handle track " + i);
                trackControls[i].prError();
            }

            // Check for interrupt after a critcial section.
            if (isInterrupted())
            {
                Log.error(realizeError);
                Log.error("  The graph building process is being interrupted.\n");
                player.processError = "interrupted while the player is being constructed.";
                return false;
            }

            trackID++;

            Log.write("\n");
        }

        // Failed if none of the tracks worked.
        if (!atLeastOneTrack)
        {
            Log.error(realizeError);
            player.processError = "input media not supported: "
                    + getCodecList();
            return false;
        }

        return true;
    }

    /**
     * doRealize Part II
     */
    protected boolean doRealize2()
    {
        // Locate the set the master time base.
        if (!manageTimeBases())
        {
            Log.error(realizeError);
            Log.error(timeBaseError);
            player.processError = timeBaseError;
            return false;
        }

        // For debugging
        Log.comment("Here's the completed flow graph:");
        traceGraph(source);
        Log.write("\n");

        profile("graph building", realizeTime);
        realizeTime = System.currentTimeMillis();

        // Update the format info on the progress control.
        updateFormats();

        profile("realize, post graph building", realizeTime);

        return true;
    }

    /**
     * The real reset code. This is a blocking call and should only be called
     * when the engine is stopped.
     */
    protected synchronized void doReset()
    {
        // We'll need to block until the modules are fully resetted to
        // move on.
        synchronized (waitResetted)
        {
            resetResettedList();

            // Put all modules in the reset state. The source module
            // is the last to receive the reset call. It will trigger
            // zero-length flush buffers to flow from the source to
            // the sinks. When the sinks receive the flush buffers,
            // it will reply back with the resetted callbacks.

            BasicModule m;
            int size = modules.size();
            for (int i = size - 1; i >= 0; i--)
            {
                m = (BasicModule) modules.elementAt(i);
                if (!m.prefetchFailed())
                    m.reset();
            }

            // The data flow (including the flush buffers) could be blocked
            // at the sinks. To initiate the reset, we'll call
            // triggerReset on the sinks.
            BasicSinkModule bsm;
            size = sinks.size();
            for (int i = 0; i < size; i++)
            {
                bsm = (BasicSinkModule) sinks.elementAt(i);
                if (!bsm.prefetchFailed())
                    bsm.triggerReset();
            }

            if (!waitResetted.isEmpty())
            {
                try
                {
                    // Wait at most 3 seconds in case anything goes
                    // wrong at the plugins. We have no control on
                    // how plugin writers write their plugin's.
                    waitResetted.wait(3000);
                } catch (Exception e)
                {
                }
            }

            // Some of the sinks may need to be stop again.
            size = sinks.size();
            for (int i = 0; i < size; i++)
            {
                bsm = (BasicSinkModule) sinks.elementAt(i);
                if (!bsm.prefetchFailed())
                    bsm.doneReset();
            }
        }

        prefetched = false;
    }

    @Override
    protected void doSetMediaTime(Time when)
    {
        slaveClock.setMediaTime(when);

        // Set media time on the renderer modules and the source.
        // Check the return time from the parser.
        Time t;
        if ((t = source.setPosition(when, 0)) == null)
            t = when;

        int size = sinks.size();
        BasicSinkModule bsm;
        for (int i = 0; i < size; i++)
        {
            bsm = (BasicSinkModule) sinks.elementAt(i);
            bsm.doSetMediaTime(when);
            bsm.setPreroll(when.getNanoseconds(), t.getNanoseconds());
        }
    }

    @Override
    public synchronized float doSetRate(float r)
    {
        if (r <= 0f)
            r = 1.0f;

        if (r == rate)
            return r;

        // If the system clock is in use, we need to set rate
        // on the system clock. Otherwise, set rate on the
        // master clock and let it determine the actual rate use.
        if (masterSink == null)
            r = getClock().setRate(r);
        else
            r = masterSink.doSetRate(r);

        BasicModule m;
        int size = modules.size();
        for (int i = 0; i < size; i++)
        {
            m = (BasicModule) modules.elementAt(i);
            if (m != masterSink)
                m.doSetRate(r);
        }

        rate = r;

        return r;
    }

    /**
     * Start immediately. Invoked from start(tbt) when the scheduled start time
     * is reached. Use the public start(tbt) method for the public interface.
     * Override this to implement subclass behavior.
     */
    @Override
    protected synchronized void doStart()
    {
        if (started)
            return;

        doStart1();
        doStart2();
    }

    /**
     * doStart - Part I.
     */
    protected void doStart1()
    {
        // If the data source is a capture device, we would like
        // to flush the data source before starting.
        if (dsource instanceof CaptureDevice && !isRTP())
            reset();

        resetPrefetchedList();
        resetStoppedList();
        resetEndedList();

        for (int i = 0; i < trackControls.length; i++)
        {
            if (trackControls[i].isEnabled())
                trackControls[i].startTrack();
        }
    }

    /**
     * doStart - Part II
     */
    protected void doStart2()
    {
        source.doStart();
        started = true;
        prefetched = true;
    }

    /**
     * Invoked from stop(). Override this to implement subclass behavior.
     */
    @Override
    protected synchronized void doStop()
    {
        if (!started)
            return;

        doStop1();
        doStop2();
    }

    /**
     * doStop - Part I.
     */
    protected void doStop1()
    {
        resetPrefetchedList();

        source.doStop();

        for (int i = 0; i < trackControls.length; i++)
        {
            if (trackControls[i].isEnabled())
                trackControls[i].stopTrack();
        }
    }

    /**
     * doStop - Part II.
     */
    protected void doStop2()
    {
        // If prefetching is disabled, we'll also need to do a
        // non-blocking stop on the source since stop is normally
        // called when data is prefetched.
        if (!prefetchEnabled)
            source.pause();

        started = false;
    }

    /**
     * Returns the DataSink which holds the timebase.
     */
    protected BasicSinkModule findMasterSink()
    {
        // Search for all the enabled track for a master time base.

        for (int i = 0; i < trackControls.length; i++)
        {
            if (!trackControls[i].isEnabled())
                continue;

            if (trackControls[i].rendererModule != null
                    && trackControls[i].rendererModule.getClock() != null)
            {
                return trackControls[i].rendererModule;
            }
        }

        return null;
    }

    public void formatChanged(Module src, Format oldFormat, Format newFormat)
    {
        Log.comment(src + ": input format changed: " + newFormat);

        // Handle size change independently.
        if (src instanceof BasicRendererModule
                && oldFormat instanceof VideoFormat
                && newFormat instanceof VideoFormat)
        {
            Dimension s1 = ((VideoFormat) oldFormat).getSize();
            Dimension s2 = ((VideoFormat) newFormat).getSize();
            if (s2 != null && (s1 == null || !s1.equals(s2)))
            {
                sendEvent(new SizeChangeEvent(this, s2.width, s2.height, 1.0f));
            }
        }
    }

    public void formatChangedFailure(Module src, Format oldFormat,
            Format newFormat)
    {
        // Send an internal error event for now.
        // Future: need to rebuild the graph.
        // -ivg
        if (!internalErrorOccurred)
        {
            sendEvent(new InternalErrorEvent(this, "Internal module " + src
                    + ": failed to handle a data format change!"));
            internalErrorOccurred = true;
            close();
        }
    }

    public void framesBehind(Module src, float frames, InputConnector ic)
    {
        OutputConnector oc;
        BasicFilterModule bfm;

        while (ic != null)
        {
            if ((oc = ic.getOutputConnector()) == null)
                break;
            if ((src = oc.getModule()) == null)
                break;
            if (!(src instanceof BasicFilterModule))
                break;
            bfm = (BasicFilterModule) src;
            bfm.setFramesBehind(frames);
            ic = src.getInputConnector(null);
        }
    }

    protected long getBitRate()
    {
        return source.getBitsRead();
    }

    public String getCNAME()
    {
        if (rtpInfo == null)
        {
            if ((rtpInfo = (RTPInfo) dsource
                    .getControl(RTPInfo.class.getName())) == null)
                return null;
        }

        return rtpInfo.getCNAME();
    }

    String getCodecList()
    {
        String list = "";
        Format fmt;
        for (int i = 0; i < trackControls.length; i++)
        {
            fmt = trackControls[i].getOriginalFormat();
            if (fmt == null || fmt.getEncoding() == null)
                continue;

            list += fmt.getEncoding();
            if (fmt instanceof VideoFormat)
                list += " video";
            else if (fmt instanceof AudioFormat)
                list += " audio";
            if (i + 1 < trackControls.length)
                list += ", ";
        }
        return list;
    }

    /**
     * Return a list of Control objects this Controller supports.
     * In this case, it is all the controls from all the modules controlled by
     * this engine.
     *
     * @return list of Controller controls.
     */
    @Override
    public Control[] getControls()
    {
        // build the list of controls. It is the total of all the
        // controls from each module plus the ones that are maintained
        // by the engine itself (e.g. progressControl).

        Control controls[];
        Vector cv = new Vector();
        Control c;
        Object cs[];
        Module m;
        int i, size = (modules == null ? 0 : modules.size());
        int otherSize = 0;

        // Collect controls from all the modules.
        for (i = 0; i < size; i++)
        {
            m = (Module) modules.elementAt(i);
            cs = m.getControls();
            if (cs == null)
                continue;
            for (int j = 0; j < cs.length; j++)
            {
                cv.addElement(cs[j]);
            }
        }

        size = cv.size();

        // Controls owned by the engine itself.

        if (videoEnabled())
        {
            if (frameRateControl == null)
            {
                frameRateControl = new FrameRateAdapter(player, 0f, 0f, 30f,
                        false)
                {
                    @Override
                    public Component getControlComponent()
                    {
                        return null;
                    }

                    @Override
                    public Object getOwner()
                    {
                        return player;
                    }

                    @Override
                    public float setFrameRate(float rate)
                    {
                        this.value = rate;
                        return -1f;
                    }
                };
            }
        }

        if (bitRateControl == null)
        {
            bitRateControl = new BitRateA(0, -1, -1, false);
        }
        if (frameRateControl != null)
            otherSize++;

        if (bitRateControl != null)
            otherSize++;

        if (framePositioningControl != null)
            otherSize++;

        controls = new Control[size + otherSize + trackControls.length];

        for (i = 0; i < size; i++)
            controls[i] = (Control) cv.elementAt(i);

        if (bitRateControl != null)
            controls[size++] = bitRateControl;

        if (frameRateControl != null)
            controls[size++] = frameRateControl;

        if (framePositioningControl != null)
            controls[size++] = framePositioningControl;

        // and the tracks
        for (i = 0; i < trackControls.length; i++)
        {
            controls[size + i] = trackControls[i];
        }

        return controls;
    }

    /**
     * Return the duration of the media. It's unknown until we implement a
     * particular node.
     *
     * @return the duration of the media.
     */
    @Override
    public Time getDuration()
    {
        return source.getDuration();
    }

    /**
     * Get audio gain control.
     */
    public GainControl getGainControl()
    {
        return (GainControl) getControl("javax.media.GainControl");
    }

    /**
     * Return the run-time latency. It's the time it takes for a packet to
     * travel from the first module to the last module. The time is computed in
     * nanoseconds.
     */
    public long getLatency()
    {
        return latency;
    }

    int getNumTracks()
    {
        int num = 0;
        for (int i = 0; i < trackControls.length; i++)
        {
            if (trackControls[i].isEnabled())
                num++;
        }
        return num;
    }

    /**
     * Get the plugin from a module. For debugging.
     */
    protected PlugIn getPlugIn(BasicModule m)
    {
        if (m instanceof BasicSourceModule)
            return ((BasicSourceModule) m).getDemultiplexer();

        if (m instanceof BasicFilterModule)
            return ((BasicFilterModule) m).getCodec();

        if (m instanceof BasicRendererModule)
            return ((BasicRendererModule) m).getRenderer();

        return null;
    }

    /**
     * Returns the start latency. Don't know until the particular node is
     * implemented.
     *
     * @return the start latency.
     */
    @Override
    public Time getStartLatency()
    {
        if ((state == Unrealized) || (state == Realizing))
            throwError(new NotRealizedError(
                    "Cannot get start latency from an unrealized controller"));
        return LATENCY_UNKNOWN;
    }

    /**
     * Override the parent's method to not check for realized state. There's no
     * need to.
     */
    @Override
    public TimeBase getTimeBase()
    {
        return getClock().getTimeBase();
    }

    /**
     * Get the visual component where the video is presented.
     */
    public Component getVisualComponent()
    {
        Vector visuals = new Vector(1);
        if (modules == null)
            return null;
        for (int i = 0; i < modules.size(); i++)
        {
            BasicModule bm = (BasicModule) modules.elementAt(i);
            PlugIn pi = getPlugIn(bm);
            if (pi instanceof VideoRenderer)
            {
                Component comp = ((VideoRenderer) pi).getComponent();
                if (comp != null)
                    visuals.addElement(comp);
            }
        }
        if (visuals.size() == 0)
            return null;
        else if (visuals.size() == 1)
            return (Component) visuals.elementAt(0);
        else
        {
            // Multiple components, put them into one container
            return createVisualContainer(visuals);
        }
    }

    public void internalErrorOccurred(Module src)
    {
        if (!internalErrorOccurred)
        {
            sendEvent(new InternalErrorEvent(this, "Internal module " + src
                    + " failed!"));
            internalErrorOccurred = true;
            close();
        }
    }

    /**
     * The PlaybackEngine is configurable.
     */
    @Override
    protected boolean isConfigurable()
    {
        return true;
    }

    public boolean isRTP()
    {
        if (testedRTP)
            return rtpInfo != null;

        rtpInfo = (RTPInfo) dsource.getControl(RTPInfo.class.getName());
        testedRTP = true;

        return rtpInfo != null;
    }

    /**
     * Given a chain of FilterModules, return the last one of the chain.
     */
    protected BasicModule lastModule(BasicModule bm)
    {
        OutputConnector oc;
        InputConnector ic;

        oc = bm.getOutputConnector(null);
        while (oc != null && (ic = oc.getInputConnector()) != null)
        {
            bm = (BasicModule) ic.getModule();
            oc = bm.getOutputConnector(null);
        }

        return bm;
    }

    protected synchronized void localStop()
    {
        super.stop();
    }

    /**
     * Search and update the master time base.
     */
    boolean manageTimeBases()
    {
        // Obtain a master time base from one of its SinkModules.
        masterSink = findMasterSink();

        return updateMasterTimeBase();

    }

    public void markedDataArrived(Module src, Buffer buffer)
    {
        if (src instanceof BasicSourceModule)
        {
            markedDataStartTime = getMediaNanoseconds();
        } else
        {
            long t = getMediaNanoseconds() - markedDataStartTime;
            if (t > 0 && t < 1000000000)
            {
                if (!reportOnce)
                {
                    Log.comment("Computed latency for video: " + t / 1000000
                            + " ms\n");
                    reportOnce = true;
                }
                latency = (t + latency) / 2;
            }
        }
    }

    public void mediaEnded(Module src)
    {
        if (src instanceof BasicSinkModule)
        {
            synchronized (waitEnded)
            {
                if (waitEnded.contains(src))
                    waitEnded.removeElement(src);
                if (waitEnded.isEmpty())
                {
                    // EOM.
                    started = false;
                    stopControllerOnly();
                    sendEvent(new EndOfMediaEvent(this, Started, Prefetched,
                            getTargetState(), getMediaTime()));
                    slaveClock.reset(USE_MASTER);
                } else if (src == masterSink)
                {
                    // If the master sink has finished but the
                    // not all the other tracks are finished, we'll
                    // need to switch the clock to using a backup for
                    // the rest of the media.
                    slaveClock.reset(USE_BACKUP);
                }
            }
        }
    }

    public void pluginTerminated(Module src)
    {
        if (!internalErrorOccurred)
        {
            sendEvent(new ControllerClosedEvent(this));
            internalErrorOccurred = true;
            close();
        }
    }

    /**
     * Flush (reset) the flow graph. This is a blocking call and should only be
     * called when the engine is stopped.
     */
    protected synchronized void reset()
    {
        // If a module has been blocked, we'll need
        // to return from the reset.
        if (started || !prefetched || dataPathBlocked)
            return;

        doReset();
    }

    protected void resetBitRate()
    {
        source.resetBitsRead();
    }

    /**
     * Reset the renderer lists.
     */
    private void resetEndedList()
    {
        synchronized (waitEnded)
        {
            waitEnded.removeAllElements();
            int size = sinks.size();
            BasicSinkModule bsm;
            for (int i = 0; i < size; i++)
            {
                bsm = (BasicSinkModule) sinks.elementAt(i);
                if (!bsm.prefetchFailed())
                    waitEnded.addElement(bsm);
            }
            waitEnded.notifyAll();
        }
    }

    // ////////////////////////////////
    //
    // Flow graph building routines.
    // ////////////////////////////////

    /**
     * Reset the renderer lists.
     */
    private void resetPrefetchedList()
    {
        synchronized (waitPrefetched)
        {
            waitPrefetched.removeAllElements();
            int size = sinks.size();
            BasicSinkModule bsm;
            for (int i = 0; i < size; i++)
            {
                bsm = (BasicSinkModule) sinks.elementAt(i);
                if (!bsm.prefetchFailed())
                    waitPrefetched.addElement(bsm);
            }
            waitPrefetched.notifyAll();
        }
    }

    /**
     * Reset the renderer lists.
     */
    private void resetResettedList()
    {
        // Set up the wait queue for the reset notifications first.
        // We'll wait for reset note from sinks and the source only.
        synchronized (waitResetted)
        {
            waitResetted.removeAllElements();
            int size = sinks.size();
            BasicSinkModule bsm;
            for (int i = 0; i < size; i++)
            {
                bsm = (BasicSinkModule) sinks.elementAt(i);
                if (!bsm.prefetchFailed())
                    waitResetted.addElement(bsm);
            }
            waitResetted.notifyAll();
        }
    }

    /**
     * Reset the renderer lists.
     */
    private void resetStoppedList()
    {
        synchronized (waitStopped)
        {
            waitStopped.removeAllElements();
            int size = sinks.size();
            BasicSinkModule bsm;
            for (int i = 0; i < size; i++)
            {
                bsm = (BasicSinkModule) sinks.elementAt(i);
                if (!bsm.prefetchFailed())
                    waitStopped.addElement(bsm);
            }
            waitStopped.notifyAll();
        }
    }

    public void resetted(Module src)
    {
        synchronized (waitResetted)
        {
            if (waitResetted.contains(src))
            {
                waitResetted.removeElement(src);
            }
            if (waitResetted.isEmpty())
            {
                // Everyone modules has been resetted.
                waitResetted.notifyAll();
            }
        }
    }

    /**
     * Override BasicController's setMediaTime so as not to set the media time
     * on the master clock twice.
     */
    @Override
    public synchronized void setMediaTime(Time when)
    {
        if (state < Realized)
            throwError(new NotRealizedError(
                    "Cannot set media time on a unrealized controller"));

        // Chances of this are really rare. Nevertheless, we'll guard
        // against it.
        if (when.getNanoseconds() == getMediaNanoseconds())
            return;

        // Reset everything.
        reset();

        // In case deallocate was called, we need to
        // clear the timeBeforeAbortPrefetch flag since we are
        // setting a new time.
        timeBeforeAbortPrefetch = null;

        doSetMediaTime(when);

        // Prefetch the engine again to display the first frame.
        doPrefetch();

        sendEvent(new MediaTimeSetEvent(this, when));
    }

    public void setProgressControl(ProgressControl p)
    {
        progressControl = p;
    }

    protected void setRenderBufferSize(Renderer r)
    {
        BufferControl bc = (BufferControl) r.getControl(BufferControl.class
                .getName());
        if (bc != null)
            bc.setBufferLength(2000);
    }

    /**
     * Verifies to see if the engine accepts the given source.
     */
    public void setSource(DataSource ds) throws IOException,
            IncompatibleSourceException
    {
        try
        {
            source = BasicSourceModule.createModule(ds);
        } catch (IOException ioe)
        {
            Log.warning("Input DataSource: " + ds);
            Log.warning("  Failed with IO exception: " + ioe.getMessage());
            throw ioe;
        } catch (IncompatibleSourceException ise)
        {
            Log.warning("Input DataSource: " + ds);
            Log.warning("  is not compatible with the MediaEngine.");
            Log.warning("  It's likely that the DataSource is required to extend PullDataSource;");
            Log.warning("  and that its source streams implement the Seekable interface ");
            Log.warning("  and with random access capability.");
            throw ise;
        }

        if (source == null)
            throw new IncompatibleSourceException();

        source.setController(this);
        dsource = ds;

        // If it's RTP data source, we'll disable prefetch and reset.
        if (dsource instanceof Streamable
                && !((Streamable) dsource).isPrefetchable())
        {
            prefetchEnabled = false;
            dataPathBlocked = true;
        }

        if (dsource instanceof CaptureDevice)
            prefetchEnabled = false;

    }

    /**
     * Override BasicController.setStopTime to allow for more accurate stop time
     * set.
     */
    @Override
    public void setStopTime(Time t)
    {
        if (getState() < Realized)
            throwError(new NotRealizedError(
                    "Cannot set stop time on an unrealized controller."));

        if (getStopTime() != null
                && getStopTime().getNanoseconds() != t.getNanoseconds())
            sendEvent(new StopTimeChangeEvent(this, t));

        if (getState() == Started && t != Clock.RESET
                && t.getNanoseconds() < getMediaNanoseconds())
        {
            // We have already passed the stop time.
            localStop();
            setStopTime(Clock.RESET);
            sendEvent(new StopAtTimeEvent(this, getState(), Prefetched,
                    getTargetState(), getMediaTime()));
        } else
        {
            getClock().setStopTime(t);

            int size = sinks.size();
            BasicSinkModule bsm;
            for (int i = 0; i < size; i++)
            {
                bsm = (BasicSinkModule) sinks.elementAt(i);
                bsm.setStopTime(t);
            }
        }
    }

    // ////////////////////////////////
    //
    // Inner classes
    // ////////////////////////////////

    /**
     * Override the parent's method to not check for realized state. There's no
     * need to.
     */
    @Override
    public void setTimeBase(TimeBase tb) throws IncompatibleTimeBaseException
    {
        getClock().setTimeBase(tb);
        // Set the time base on each of the SinkModules.
        if (sinks == null)
            return;
        int size = sinks.size();
        BasicSinkModule bsm;
        for (int i = 0; i < size; i++)
        {
            bsm = (BasicSinkModule) sinks.elementAt(i);
            bsm.setTimeBase(tb);
        }
    }

    /**
     * This is stop by request.
     */
    @Override
    public synchronized void stop()
    {
        super.stop();
        sendEvent(new StopByRequestEvent(this, Started, Prefetched,
                getTargetState(), getMediaTime()));
    }

    public void stopAtTime(Module src)
    {
        if (src instanceof BasicSinkModule)
        {
            synchronized (waitStopped)
            {
                if (waitStopped.contains(src))
                    waitStopped.removeElement(src);

                // Check to see if all the tracks have stopped or
                // all other tracks have reached EOM and this is the
                // only track left behind.
                if (waitStopped.isEmpty() || waitEnded.size() == 1
                        && waitEnded.contains(src))
                {
                    // Stop at time.
                    started = false;
                    stopControllerOnly();
                    setStopTime(Clock.RESET);
                    sendEvent(new StopAtTimeEvent(this, Started, Prefetched,
                            getTargetState(), getMediaTime()));
                    slaveClock.reset(USE_MASTER);
                } else if (src == masterSink)
                {
                    // If the master sink has finished but the
                    // not all the other tracks are finished, we'll
                    // need to switch the clock to using a backup for
                    // the rest of the media.
                    slaveClock.reset(USE_BACKUP);
                }
            }
        }
    }

    /**
     * Trace the flow graph for debugging.
     */
    void traceGraph(BasicModule source)
    {
        Module m;
        OutputConnector oc;
        InputConnector ic;
        String names[];

        names = source.getOutputConnectorNames();
        for (int i = 0; i < names.length; i++)
        {
            oc = source.getOutputConnector(names[i]);
            if ((ic = oc.getInputConnector()) == null)
                continue;
            if ((m = ic.getModule()) == null)
                continue;
            Log.write("  " + getPlugIn(source));
            Log.write("     connects to: " + getPlugIn((BasicModule) m));
            Log.write("     format: " + oc.getFormat());
            traceGraph((BasicModule) m);
        }
    }

    /**
     * Update the format info per track on the progress control.
     */
    public void updateFormats()
    {
        // Update formats per track.
        for (int i = 0; i < trackControls.length; i++)
        {
            trackControls[i].updateFormat();
        }
    }

    /**
     * Update the master timebase on all the modules.
     */
    boolean updateMasterTimeBase()
    {
        BasicSinkModule bsm;
        int size = sinks.size();

        if (masterSink != null)
            slaveClock.setMaster(masterSink.getClock());
        else
            // No master sink found in the module, we'll use the
            // system time base.
            slaveClock.setMaster(null);

        // Set the master time base to each of the SinkModules.
        for (int i = 0; i < size; i++)
        {
            bsm = (BasicSinkModule) sinks.elementAt(i);
            if (bsm != masterSink && !bsm.prefetchFailed())
            {
                try
                {
                    bsm.setTimeBase(slaveClock.getTimeBase());
                } catch (IncompatibleTimeBaseException e)
                {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Update the aggregate bit rate and frame rate per track on the progress
     * control.
     */
    public void updateRates()
    {
        if (getState() < Realized)
            return;

        // Update global bit rate.
        long now = System.currentTimeMillis();
        long rate, avg;

        if (now == lastStatsTime)
            rate = lastBitRate;
        else
            rate = (long) (getBitRate() * 8.0 / (now - lastStatsTime) * 1000.0);
        avg = (lastBitRate + rate) / 2;

        if (bitRateControl != null)
        {
            bitRateControl.setBitRate((int) avg);
        }

        lastBitRate = rate;
        lastStatsTime = now;
        resetBitRate();

        // Update frame rate on a per-track basis.
        for (int i = 0; i < trackControls.length; i++)
        {
            trackControls[i].updateRates(now);
        }

        // Compute the instanteous latency.
        source.checkLatency();
    }

    /**
     * Return true if video is present.
     */
    public boolean videoEnabled()
    {
        for (int i = 0; i < trackControls.length; i++)
        {
            if (trackControls[i].isEnabled()
                    && trackControls[i].getOriginalFormat() instanceof VideoFormat)
                return true;
        }
        return false;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy