/* * @(#)ProcessEngine.java 1.16 02/08/21 * * Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved. */ package com.sun.media; import java.io.*; import java.util.*; import java.awt.*; import javax.media.*; import javax.media.protocol.*; import javax.media.control.*; import javax.media.format.*; import javax.media.renderer.*; import com.sun.media.util.*; import com.sun.media.controls.*; import com.sun.media.renderer.audio.AudioRenderer; import com.sun.media.codec.video.colorspace.RGBScaler; /** * ProcessEngine implements the media engine for processors. */ public class ProcessEngine extends PlaybackEngine { protected BasicMuxModule muxModule; protected ContentDescriptor outputContentDes = null; public ProcessEngine(BasicProcessor p) { super(p); } /** * Configuring the engine. */ 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 ProcTControl(this, tracks[i], source.getOutputConnector(names[i])); } if (!doConfigure2()) return false; // By default a Processor generates RAW output. outputContentDes = new ContentDescriptor(ContentDescriptor.RAW); // The parser disables the hint tracks by default. We'll // re-enable them. reenableHintTracks(); return true; } /** * @return true if successful. */ protected synchronized boolean doRealize() { // Reset the target multiplexers targetMuxes = null; if (!super.doRealize1()) return false; // Connect the tracks to a multiplexer, if there's one. if (targetMux != null && !connectMux()) { Log.error(realizeError); Log.error(" Cannot connect the multiplexer\n"); player.processError = genericProcessorError; return false; } if (!super.doRealize2()) return false; return true; } /** * Return true if the given format is RTP related. */ boolean isRTPFormat(Format fmt) { return fmt != null && fmt.getEncoding() != null && fmt.getEncoding().endsWith("rtp") || fmt.getEncoding().endsWith("RTP"); } void reenableHintTracks() { for (int i = 0; i < trackControls.length; i++) { if (isRTPFormat(trackControls[i].getOriginalFormat())) { trackControls[i].setEnabled(true); break; } } } BasicMuxModule getMuxModule() { return muxModule; } /** * Connect the multiplexer. */ boolean connectMux() { /** * The target Mux has already been determined. We'll just * hook it up. */ BasicTrackControl tcs[] = new BasicTrackControl[trackControls.length]; int total = 0; Multiplexer mux = (Multiplexer)targetMux.plugin; for (int i = 0; i < trackControls.length; i++) { if (trackControls[i].isEnabled()) { tcs[total++] = trackControls[i]; } } try { mux.setContentDescriptor(outputContentDes); } catch (Exception e) { Log.comment("Failed to set the output content descriptor on the multiplexer."); return false; } boolean failed = false; if (mux.setNumTracks(targetMuxFormats.length) != targetMuxFormats.length) { Log.comment("Failed to set number of tracks on the multiplexer."); return false; } for (int mf = 0; mf < targetMuxFormats.length; mf++) { if (targetMuxFormats[mf] == null || mux.setInputFormat(targetMuxFormats[mf], mf) == null) { Log.comment("Failed to set input format on the multiplexer."); failed = true; break; } } if (failed) return false; if (SimpleGraphBuilder.inspector != null && !SimpleGraphBuilder.inspector.verify(mux, targetMuxFormats)) return false; //Log.comment("Found multiplexer: " + mux); InputConnector ic; BasicMuxModule bmm = new BasicMuxModule(mux, targetMuxFormats); if (DEBUG) bmm.setJMD(jmd); String name; // Make the connections. for (int j = 0; j < targetMuxFormats.length; j++) { ic = bmm.getInputConnector(BasicMuxModule.ConnectorNamePrefix + j); if (ic == null) { // Something is terribly wrong. Log.comment("BasicMuxModule: connector mismatched."); return false; } ic.setFormat(targetMuxFormats[j]); tcs[j].lastOC.setProtocol(ic.getProtocol()); tcs[j].lastOC.connectTo(ic, targetMuxFormats[j]); } if (!bmm.doRealize()) { //Log.comment("Failed to open the multiplexer."); return false; } bmm.setModuleListener(this); bmm.setController(this); modules.addElement(bmm); sinks.addElement(bmm); muxModule = bmm; return true; } /** * Search and update the master time base. */ protected BasicSinkModule findMasterSink() { // Obtain a master time base from one of its SinkModules. if (muxModule != null && muxModule.getClock() != null) { return muxModule; } return super.findMasterSink(); } String prefetchError = "Failed to prefetch: " + this; /** * The stub function to perform the steps to prefetch the controller. * @return true if successful. */ protected synchronized boolean doPrefetch() { if (prefetched) return true; if (!doPrefetch1()) return false; // Fail if the mux module cannot be prefetched. if (muxModule != null && !muxModule.doPrefetch()) { Log.error(prefetchError); Log.error(" Cannot prefetch the multiplexer: " + muxModule.getMultiplexer() + "\n"); return false; } return doPrefetch2(); } /** * 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. */ protected synchronized void doStart() { if (started) return; doStart1(); if (muxModule != null) muxModule.doStart(); doStart2(); } /** * Invoked from stop(). * Override this to implement subclass behavior. */ protected synchronized void doStop() { if (!started) return; doStop1(); if (muxModule != null) muxModule.doStop(); doStop2(); } /** * Get the track controls. */ public TrackControl [] getTrackControls() throws NotConfiguredError { if (getState() < Configured) throwError(new NotConfiguredError("getTrackControls " + NOT_CONFIGURED_ERROR)); return trackControls; } /** * Return all the content-types which this Processor's output supports. */ public ContentDescriptor[] getSupportedContentDescriptors() throws NotConfiguredError { if (getState() < Configured) throwError(new NotConfiguredError("getSupportedContentDescriptors " + NOT_CONFIGURED_ERROR)); Vector names = PlugInManager.getPlugInList(null, null, PlugInManager.MULTIPLEXER); Vector fmts = new Vector(); Format fs[]; int i, j, k; boolean duplicate; for (i = 0; i < names.size(); i++) { fs = PlugInManager.getSupportedOutputFormats( (String)names.elementAt(i), PlugInManager.MULTIPLEXER); if (fs == null) continue; for (j = 0; j < fs.length; j++) { if (!(fs[j] instanceof ContentDescriptor)) continue; duplicate = false; for (k = 0; k < fmts.size(); k++) { if (fmts.elementAt(k).equals(fs[j])) { duplicate = true; break; } } if (!duplicate) fmts.addElement(fs[j]); } } ContentDescriptor cds[] = new ContentDescriptor[fmts.size()]; for (i = 0; i < fmts.size(); i++) cds[i] = (ContentDescriptor)fmts.elementAt(i); return cds; } /** * Set the output content-type. */ public ContentDescriptor setContentDescriptor(ContentDescriptor ocd) throws NotConfiguredError { if (getState() < Configured) throwError(new NotConfiguredError("setContentDescriptor " + NOT_CONFIGURED_ERROR)); if (getState() > Configured) return null; if (ocd != null) { Vector cnames = PlugInManager.getPlugInList(null, ocd, PlugInManager.MULTIPLEXER); if (cnames == null || cnames.size() == 0) return null; } outputContentDes = ocd; return outputContentDes; } /** * Return the output content-type. */ public ContentDescriptor getContentDescriptor() throws NotConfiguredError { if (getState() < Configured) throwError(new NotConfiguredError("getContentDescriptor " + NOT_CONFIGURED_ERROR)); return outputContentDes; } /** * Return the output DataSource of the Processor. */ public DataSource getDataOutput() throws NotRealizedError { if (getState() < Controller.Realized) throwError(new NotRealizedError("getDataOutput " + NOT_REALIZED_ERROR)); if (muxModule != null) return muxModule.getDataOutput(); else return null; } /** * Report the output bit rate if a mux is used. */ protected long getBitRate() { if (muxModule != null) return muxModule.getBitsWritten(); else return source.getBitsRead(); } protected void resetBitRate() { if (muxModule != null) muxModule.resetBitsWritten(); else source.resetBitsRead(); } ////////////////////////////////// // // Flow graph building routines. ////////////////////////////////// /** * Get the plugin from a module. For debugging. */ protected PlugIn getPlugIn(BasicModule m) { if (m instanceof BasicMuxModule) return ((BasicMuxModule)m).getMultiplexer(); return super.getPlugIn(m); } ////////////////////////////////// // // Inner classes ////////////////////////////////// class ProcTControl extends BasicTrackControl implements Owned { // Customized options. protected Format formatWanted = null; protected Codec codecChainWanted[] = null; protected Renderer rendererWanted = null; protected ProcGraphBuilder gb; protected Format supportedFormats[] = null; public ProcTControl(ProcessEngine engine, Track track, OutputConnector oc) { super(engine, track, oc); } public Object getOwner() { return player; } public Format getFormat() { return (formatWanted == null ? track.getFormat() : formatWanted); } public Format [] getSupportedFormats() { // First check to see if we have already computed the supported // formats for the track format in the past. If not, then we'll // compute the supported formats using the graph builder. if (supportedFormats == null && (supportedFormats = Resource.getDB(track.getFormat())) == null) { if (gb == null) gb = new ProcGraphBuilder((ProcessEngine)engine); else gb.reset(); supportedFormats = gb.getSupportedOutputFormats(track.getFormat()); supportedFormats = Resource.putDB(track.getFormat(), supportedFormats); needSavingDB = true; } // If an output content descriptor is given, we'll need to // verify if the mux support individual inputs. if (outputContentDes != null) { return verifyMuxInputs(outputContentDes, supportedFormats); } else return supportedFormats; } /** * If a multiplexer (ContentDescriptor) is specified, we'll verify * if the multiplexer supports the given input. */ Format [] verifyMuxInputs(ContentDescriptor cd, Format inputs[]) { if (cd == null || cd.getEncoding() == ContentDescriptor.RAW) return inputs; // Instantiate all the multiplexers that support the // given output content descriptor. Vector cnames = PlugInManager.getPlugInList(null, cd, PlugInManager.MULTIPLEXER); if (cnames == null || cnames.size() == 0) return new Format[0]; Multiplexer mux[] = new Multiplexer[cnames.size()]; int total = 0; Multiplexer m; for (int i = 0; i < cnames.size(); i++) { if ((m = (Multiplexer)SimpleGraphBuilder.createPlugIn((String)cnames.elementAt(i), PlugInManager.MULTIPLEXER)) != null) { try { m.setContentDescriptor(outputContentDes); } catch (Exception e) { continue; } if (m.setNumTracks(1) < 1) continue; mux[total++] = m; } } // Query the multiplexers to see if they support the input // format. Format tmp[] = new Format[inputs.length]; Format fmt; int vtotal = 0; for (int i = 0; i < inputs.length; i++) { if (total == 1) { // Let's do some loop unrolling. if ((fmt = mux[0].setInputFormat(inputs[i], 0)) != null) tmp[vtotal++] = fmt; } else { for (int j = 0; j < total; j++) { if ((fmt = mux[j].setInputFormat(inputs[i], 0)) != null) { tmp[vtotal++] = fmt; break; } } } } Format verified[] = new Format[vtotal]; System.arraycopy(tmp, 0, verified, 0, vtotal); return verified; } public Format setFormat(Format format) { if (engine.getState() > Configured) return getFormat(); /* Force a new size for testing. if (format instanceof VideoFormat) { VideoFormat newf = new VideoFormat(null, new Dimension(100, 100), Format.NOT_SPECIFIED, null, Format.NOT_SPECIFIED); format = newf.intersects(format); } */ if (format != null && !format.matches(track.getFormat())) { formatWanted = checkSize(format); } else return format; /* Format fmts[] = getSupportedFormats(); boolean good = false; for (int i = 0; i < fmts.length; i++) { if (formatWanted.matches(fmts[i])) { System.err.println("It's a good format"); good = true; } } if (!good) { System.err.println("Format set: " + formatWanted); System.err.println("It's a bad format"); } */ return formatWanted; } /** * Check if the video size of the given format is valid. * If not, return a format with the correct size. */ private Format checkSize(Format fmt) { if (!(fmt instanceof VideoFormat)) return fmt; VideoFormat vfmt = (VideoFormat)fmt; Dimension size = ((VideoFormat)fmt).getSize(); if (size == null) { Format ofmt = getOriginalFormat(); if (ofmt == null || (size = ((VideoFormat)ofmt).getSize()) == null) return fmt; } int w = size.width, h = size.height; if (fmt.matches(new VideoFormat(VideoFormat.JPEG_RTP)) || fmt.matches(new VideoFormat(VideoFormat.JPEG))) { // JPEG sizes should be a multiple of 8. if (size.width % 8 != 0) w = size.width / 8 * 8; if (size.height % 8 != 0) h = size.height / 8 * 8; if (w == 0 || h == 0) { w = size.width; h = size.height; } } else if (fmt.matches(new VideoFormat(VideoFormat.H263_RTP)) || fmt.matches(new VideoFormat(VideoFormat.H263_1998_RTP)) || fmt.matches(new VideoFormat(VideoFormat.H263))) { // H.263 sizes are pretty rigid. if (size.width >= 352) { w = 352; h = 288; } else if (size.width >= 160) { w = 176; h = 144; } else { w = 128; h = 96; } } if (w != size.width || h != size.height) { Log.comment("setFormat: " + fmt.getEncoding() + ": video aspect ratio mismatched."); Log.comment(" Scaled from " + size.width + "x" + size.height + " to " + w + "x" + h + ".\n"); fmt = (new VideoFormat(null, new Dimension(w, h), Format.NOT_SPECIFIED, null, Format.NOT_SPECIFIED)).intersects(fmt); } return fmt; } /** * Top level routine to build a single track. */ public boolean buildTrack(int trackID, int numTracks) { if (gb == null) gb = new ProcGraphBuilder((ProcessEngine)engine); else gb.reset(); boolean rtn = gb.buildGraph(this, trackID, numTracks); // dispose the old GraphBuilder after building a track. // The cache is not valid anymore. gb = null; return rtn; } /** * Returns true if this track holds the master time base. */ public boolean isTimeBase() { for (int j = 0; j < modules.size(); j++) { if (modules.elementAt(j) == masterSink) return true; } return false; } public void setCodecChain(Codec codec[]) throws NotConfiguredError, UnsupportedPlugInException { if (engine.getState() > Configured) throwError(new NotConfiguredError(connectErr)); if (codec.length < 1) throw new UnsupportedPlugInException("No codec specified in the array."); codecChainWanted = new Codec[codec.length]; for (int i = 0; i < codec.length; i++) codecChainWanted[i] = codec[i]; } public void setRenderer(Renderer renderer) throws NotConfiguredError { if (engine.getState() > Configured) throwError(new NotConfiguredError(connectErr)); this.rendererWanted = renderer; if (renderer instanceof SlowPlugIn) ((SlowPlugIn)renderer).forceToUse(); } public boolean isCustomized() { return formatWanted != null || codecChainWanted != null || rendererWanted != null; } public void prError() { if (!isCustomized()) { super.prError(); return; } Log.error(" Cannot build a flow graph with the customized options:"); if (formatWanted != null) { Log.error(" Unable to transcode format: " + getOriginalFormat()); Log.error(" to: " + getFormat()); if (outputContentDes != null) Log.error(" outputting to: " + outputContentDes); } if (codecChainWanted != null) { Log.error(" Unable to add customed codecs: "); for (int i = 0; i < codecChainWanted.length; i++) Log.error(" " + codecChainWanted[i]); } if (rendererWanted != null) { Log.error(" Unable to add customed renderer: " + rendererWanted); } Log.write("\n"); } protected ProgressControl progressControl() { return progressControl; } protected FrameRateControl frameRateControl() { this.muxModule = getMuxModule(); return frameRateControl; } } /** * This is the Graph builder to generate the data flow graph for * the media engine. It extends from the SimpleGraphBuilder to * handle multiplexers, customized output formats, codecs and renderers. * * It contains 3 parts: * 1) Routines to search for all the supported output formats; * 2) Routines to build a default flow graph -- buildGraph; * 3) Routines to build a custom flow graph -- buildCustomGraph. * * A default graph is such that no customised option is specified on * the TrackControl. * */ // The list of target multiplexers. protected Vector targetMuxNames = null; protected GraphNode targetMuxes[] = null; protected GraphNode targetMux = null; protected Format targetMuxFormats[] = null; class ProcGraphBuilder extends SimpleGraphBuilder { protected ProcessEngine engine; protected Format targetFormat; protected int trackID = 0; protected int numTracks = 1; protected int nodesVisited = 0; ProcGraphBuilder(ProcessEngine engine) { this.engine = engine; } /** * Given an input format, find out all the supported output formats * by searching through the node graph. */ public Format [] getSupportedOutputFormats(Format input) { long formatsTime = System.currentTimeMillis(); Vector collected = new Vector(); Vector candidates = new Vector(); GraphNode node = new GraphNode(null, (PlugIn)null, input, null, 0); candidates.addElement(node); collected.addElement(input); nodesVisited++; while (!candidates.isEmpty()) doGetSupportedOutputFormats(candidates, collected); // Convert the resulting vector into an array. Format all[] = new Format[collected.size()]; // The following bit of code is a hack to put MPEG/RTP the // the end of the supported list since it's the least robust // RTP codecs. int front = 0, back = all.length - 1; Format mpegAudio = new AudioFormat(AudioFormat.MPEG_RTP); boolean mpegInput = (new AudioFormat(AudioFormat.MPEG)).matches(input) || (new AudioFormat(AudioFormat.MPEGLAYER3)).matches(input) || (new VideoFormat(VideoFormat.MPEG)).matches(input); for (int i = 0; i < all.length; i++) { Object obj = collected.elementAt(i); if (!mpegInput && mpegAudio.matches((Format)obj)) all[back--] = (Format)obj; else all[front++] = (Format)obj; } Log.comment("Getting the supported output formats for:"); Log.comment(" " + input); Log.comment(" # of nodes visited: " + nodesVisited); Log.comment(" # of formats supported: " + all.length + "\n"); engine.profile("getSupportedOutputFormats", formatsTime); return all; } /** * Collect the supported output formats from the list of node * candidates. */ void doGetSupportedOutputFormats(Vector candidates, Vector collected) { GraphNode node = (GraphNode)candidates.firstElement(); candidates.removeElementAt(0); if (node.input == null && (node.plugin == null || !(node.plugin instanceof Codec))) { // shouldn't happen! Log.error("Internal error: doGetSupportedOutputFormats"); return; } if (node.plugin != null) { // It may not seem necessary to do this since the // previous round has already verified the input. // But since the same plugin could have a different // input called on it on previous rounds, it needs to // be resetted to the designated input. This has // caused a bug in failing setOutputFormat for some // codecs. if (verifyInput(node.plugin, node.input) == null) return; } Format input, outs[]; if (node.plugin != null) { outs = node.getSupportedOutputs(node.input); if (outs == null || outs.length == 0) return; // Add the output formats to the collected list and // check for duplication. boolean found; int j, k, size; Format other; for (j = 0; j < outs.length; j++) { size = collected.size(); found = false; for (k = 0; k < size; k++) { other = (Format)collected.elementAt(k); if (other == outs[j] || other.equals(outs[j])) { found = true; break; } } if (!found) collected.addElement(outs[j]); } input = node.input; } else { outs = new Format[1]; outs[0] = node.input; input = null; } // Don't go deeper than allowed. if (node.level >= STAGES) return; GraphNode gn, n; Format fmt, ins[]; for (int i = 0; i < outs.length; i++) { // Ignore outputs that are the same as the input. if (input != null && input.equals(outs[i])) continue; // Verify the output format. if (node.plugin != null && verifyOutput(node.plugin, outs[i]) == null) { continue; } Vector cnames = PlugInManager.getPlugInList(outs[i], null, PlugInManager.CODEC); if (cnames == null || cnames.size() == 0) continue; for (int j = 0; j < cnames.size(); j++) { // Instantiate and verify the codec. if ((gn = getPlugInNode((String)cnames.elementAt(j), PlugInManager.CODEC, plugIns)) == null) continue; // Check to see if the particular input/plugin combination // has already been attempted. If so, we don't need to // do it again. if (gn.checkAttempted(outs[i])) continue; ins = gn.getSupportedInputs(); if ((fmt = matches(outs[i], ins, null, gn.plugin)) == null) continue; n = new GraphNode(gn, fmt, node, node.level+1); candidates.addElement(n); nodesVisited++; } } } boolean buildGraph(BasicTrackControl tc, int trackID, int numTracks) { this.trackID = trackID; this.numTracks = numTracks; // If the custom options are specified, we'll use the // different routine that's specialized for this. if (tc.isCustomized()) { Log.comment("Input: " + tc.getOriginalFormat()); return buildCustomGraph((ProcTControl)tc); } return super.buildGraph(tc); } protected GraphNode buildTrackFromGraph(BasicTrackControl tc, GraphNode node) { return engine.buildTrackFromGraph((ProcTControl)tc, node); } /** * This defines when the search ends. The "targets" array defines * the nodes that are to be the "end points" (leaf nodes) of the * graph. * With the default graph builder, the targets array contains the * list of sinks that can potentially support the input format. */ GraphNode findTarget(GraphNode node) { Format outs[]; // Expand the outputs of the next node. if (node.plugin == null) { outs = new Format[1]; outs[0] = node.input; } else { if (node.output != null) { outs = new Format[1]; outs[0] = node.output; } else { outs = node.getSupportedOutputs(node.input); if (outs == null || outs.length == 0) { //Log.write("Weird! The given plugin does not support any output."); return null; } } } // If there's a constraint format, check for that first. if (targetFormat != null) { Format matched = null; if ((matched = matches(outs, targetFormat, node.plugin, null)) == null) return null; if (inspector != null && !inspector.verify((Codec)node.plugin, node.input, matched)) return null; // If there's no more targets to match, we are done. if (targetPlugins == null && targetMuxes == null) { node.output = matched; return node; } // The matching target format is chosen. outs = new Format[1]; outs[0] = matched; } GraphNode n; // Check for the list of predefined targets. if (targetPlugins != null) { if ((n = verifyTargetPlugins(node, outs)) != null) return n; else return null; } // Check for the list of predefined muxes. if (targetMuxes != null && (n = verifyTargetMuxes(node, outs)) != null) return n; return null; } /** * If a multiplexer is specified, check for that. */ GraphNode verifyTargetMuxes(GraphNode node, Format outs[]) { Multiplexer mux; GraphNode gn; Format fmt; for (int i = 0; i < targetMuxes.length; i++) { if ((gn = targetMuxes[i]) == null) { String name = (String)targetMuxNames.elementAt(i); if (name == null) continue; // We'll want to instantiate it to get more info from it. if ((gn = getPlugInNode(name, PlugInManager.MULTIPLEXER, plugIns)) == null) { targetMuxNames.setElementAt(null, i); continue; } mux = (Multiplexer)gn.plugin; if (mux.setContentDescriptor(outputContentDes) == null) { targetMuxNames.setElementAt(null, i); continue; } if (mux.setNumTracks(numTracks) != numTracks) { targetMuxNames.setElementAt(null, i); continue; } targetMuxes[i] = gn; } if (targetMux != null && gn != targetMux) continue; for (int j = 0; j < outs.length; j++) { if ((fmt = ((Multiplexer)gn.plugin).setInputFormat(outs[j], trackID)) == null) continue; // found the target. if (inspector != null) { if (node.plugin != null && !inspector.verify((Codec)node.plugin, node.input, fmt)) continue; } targetMux = gn; targetMuxFormats[trackID] = fmt; node.output = fmt; return node; } } return null; } /** * Set the default targets, which are the renderers. */ boolean setDefaultTargets(Format in) { // If there's an output content descriptor specified, // The targets will be a list of multiplexers. if (outputContentDes != null) return setDefaultTargetMux(); else return setDefaultTargetRenderer(in); } boolean setDefaultTargetRenderer(Format in) { if (!super.setDefaultTargetRenderer(in)) return false; targetMuxes = null; return true; } boolean setDefaultTargetMux() { // If the target muxes are already defined, we don't need // to do that again. if (targetMuxes != null) return true; Log.comment("An output content type is specified: " + outputContentDes); targetMuxNames = PlugInManager.getPlugInList(null, outputContentDes, PlugInManager.MULTIPLEXER); if (targetMuxNames == null || targetMuxNames.size() == 0) { Log.error("No multiplexer is found for that content type: " + outputContentDes); return false; } targetMuxes = new GraphNode[targetMuxNames.size()]; targetMux = null; targetMuxFormats = new Format[numTracks]; // The regular targets will not be used. targetPluginNames = null; targetPlugins = null; return true; } /** * Set the target to a custom plugin specified. */ void setTargetPlugin(PlugIn p, int type) { targetPlugins = new GraphNode[1]; targetPlugins[0] = new GraphNode(p, null, null, 0); targetPlugins[0].custom = true; targetPlugins[0].type = type; } /****************************************** * * Routines for building custom graphs * ******************************************/ Codec codecs[] = null; Renderer rend = null; Format format = null; boolean buildCustomGraph(ProcTControl tc) { this.codecs = tc.codecChainWanted; this.rend = tc.rendererWanted; this.format = tc.formatWanted; if (format instanceof VideoFormat && tc.getOriginalFormat() instanceof VideoFormat) { Dimension s1 = ((VideoFormat)tc.getOriginalFormat()).getSize(); Dimension s2 = ((VideoFormat)format).getSize(); if (s1 != null && s2 != null && !s1.equals(s2)) { // The video needs to be resized. // We'll instantiate the video scaler then // insert it into the flow graph. RGBScaler scaler = new RGBScaler(s2); if (codecs == null || codecs.length == 0) { codecs = new Codec[1]; codecs[0] = scaler; } else { // There are some custom codecs specified. // We'll use some simple heuristics to determine // where to insert the scaler. codecs = new Codec[tc.codecChainWanted.length + 1]; int i; if (!isRawVideo(format)) { // The destination format is not a raw format. // we'll insert the scaler at the front. codecs[0] = scaler; i = 1; } else { codecs[tc.codecChainWanted.length] = scaler; i = 0; } for (int j = 0; j < tc.codecChainWanted.length; j++) codecs[i++] = tc.codecChainWanted[j]; } } } GraphNode node, failed; return ((node = buildCustomGraph(tc.getOriginalFormat())) != null) && ((failed = buildTrackFromGraph(tc, node)) == null); } /** * Overrides GraphBuider's buildGraph method. * The major difference is, it pay attentions to the effects, * codecs, renderers specified. */ GraphNode buildCustomGraph(Format in) { Vector candidates = new Vector(); GraphNode node, n = null; Format fmt, fmts[]; // root. node = new GraphNode(null, (PlugIn)null, in, null, 0); candidates.addElement(node); Log.comment("Custom options specified."); indent = 1; Log.setIndent(indent); // Handle custom codec chain. if (codecs != null) { resetTargets(); for (int i = 0; i < codecs.length; i++) { if (codecs[i] == null) continue; Log.comment("A custom codec is specified: " + codecs[i]); // Set the custom target to be the next codec specified. setTargetPlugin(codecs[i], PlugInManager.CODEC); if ((node = buildGraph(candidates)) == null) { Log.error("The input format is not compatible with the given codec plugin: " + codecs[i]); indent = 0; Log.setIndent(indent); return null; } node.level = 0; candidates = new Vector(); candidates.addElement(node); } } if (outputContentDes != null) { resetTargets(); // Set the target format. if (format != null) { targetFormat = format; Log.comment("An output format is specified: " + format); } // A mux is specified. if (!setDefaultTargetMux()) return null; if ((node = buildGraph(candidates)) == null) { Log.error("Failed to build a graph for the given custom options."); indent = 0; Log.setIndent(indent); return null; } } else { if (format != null) { // A target format is set. First find a route to // to transcode to the target format. resetTargets(); targetFormat = format; Log.comment("An output format is specified: " + format); if ((node = buildGraph(candidates)) == null) { Log.error("The input format cannot be transcoded to the specified target format."); indent = 0; Log.setIndent(indent); return null; } node.level = 0; candidates = new Vector(); candidates.addElement(node); targetFormat = null; } // Connect the rest of the graph to a renderer. if (rend != null) { // Handle custom renderer. Log.comment("A custom renderer is specified: " + rend); // Set the custom target to be the renderer specified. setTargetPlugin(rend, PlugInManager.RENDERER); if ((node = buildGraph(candidates)) == null) { if (format != null) Log.error("The customed transocoded format is not compatible with the given renderer plugin: " + rend); else Log.error("The input format is not compatible with the given renderer plugin: " + rend); indent = 0; Log.setIndent(indent); return null; } } else { // Handle the default renderers. if (!setDefaultTargetRenderer(format == null ? in : format)) return null; if ((node = buildGraph(candidates)) == null) { if (format != null) Log.error("Failed to find a renderer that supports the customed transcoded format."); else Log.error("Failed to build a graph to render the input format with the given custom options."); indent = 0; Log.setIndent(indent); return null; } } } indent = 0; Log.setIndent(indent); return node; } public void reset() { super.reset(); resetTargets(); } /** * Reset all the targets. */ void resetTargets() { targetFormat = null; targetPlugins = null; } } }