📄 filtergraph.java
字号:
if (BEST_FIRST_SEARCH_DEPTH > 0) { // now, increasing depths of best-first search. // the reason for not doing a straight best-first search is that suboptimal graphs like the above (in comments) can be found. for (int cutoffDepth = 0; cutoffDepth < BEST_FIRST_SEARCH_DEPTH; ++cutoffDepth) { // first, do best-first search FilterGraphNode n = findCodecPathTo(destPlugInType, f, from, new HashSet(), mux, muxInputFormat, muxDestTrack, depth, cutoffDepth, BEST_FIRST_SEARCH_BREADTH); if (n != null) { logger.fine("Graph found with best-first search."); return n; } } logger.fine("Graph not found with best-first search, trying incrementally deeper breadth-first searches"); } // now, increasing depths of breadth-first search. for (int cutoffDepth = TYPICAL_GRAPH_DEPTH; cutoffDepth < MAX_GRAPH_DEPTH; ++cutoffDepth) { //System.out.println("Trying depth " + cutoffDepth); FilterGraphNode n = findCodecPathTo(destPlugInType, f, from, new HashSet(), mux, muxInputFormat, muxDestTrack, depth, cutoffDepth, -1); if (n != null) return n; } logger.warning("Filter graph search depth at " + MAX_GRAPH_DEPTH + ", abandoning deeper search"); // just to prevent infinite search. Not sure this can actually happen, this is a safeguard. return null; } private static final boolean SKIP_NON_FORMAT_CHANGING_CODECS = true; // TODO: format between one node's output and another node's input must be negotiated! // A->B: f = a.getOutputFormat(), f = b.setInputFormat(f), if f differs, f = f.setOutputFormat(f), etc! // excludeFormatAtDepths is used to incrementally add plug ins which are a dead end - no path to renderer, to avoid retracing our steps. // maxBestCodecs is used for best-first search. Only applies when the muxInputFormat is known. If not -1, only the top x codecs will be tried. // this limits the breadth of the search tree and allows us to go deeper. private static FilterGraphNode findCodecPathTo(int destPlugInType, final Format f, final PlugIn from, final Set excludeFormatAtDepths, final Multiplexer mux, final Format muxInputFormat, final int muxDestTrack, final int depth, final int cutoffDepth, final int maxBestCodecs) { if (f == null) throw new NullPointerException(); if (depth >= cutoffDepth) // for our search, we have reached the depth limit. { return null; } if (excludeFormatAtDepths.contains(new FormatAtDepth(f, depth))) // already tried this format at this depth, no luck. return null; if (destPlugInType == PlugInManager.RENDERER) { final Renderer renderer = findRenderer(f); if (renderer != null) { final Format fAccepted = negotiate(f, renderer, from); if (fAccepted != null) { BufferControl bufferControl = (BufferControl) renderer.getControl("javax.media.control.BufferControl"); if (bufferControl != null) { bufferControl.setBufferLength(2000); // TODO: hard-coded for testing } return new RendererNode(renderer, fAccepted); } } } else if (destPlugInType == PlugInManager.MULTIPLEXER) { final Format fAccepted = negotiate(f, mux, muxInputFormat, muxDestTrack, from); if (fAccepted != null) { return new MuxNode(mux, fAccepted); } } else throw new IllegalArgumentException(); // if we call again recursively, we will hit the depth limit, so just stop here. We've already checked // whether we can make a terminal node with a renderer/mux. If we get here, it would have to add // a codec below to the chain, before a renderer/mux, which would be 2 nodes. if (depth >= cutoffDepth - 1) // for our search, we have reached the depth limit. { return null; } final Vector codecs = PlugInManager.getPlugInList(f, null, PlugInManager.CODEC); for (int j = 0; j < codecs.size(); ++j) { final String codecClassName = (String) codecs.get(j); if (TRACE) logger.finest(indent(depth) + "Found codec for " + f + ": " + codecClassName); } // first, put all the possible codecs and output formats as pairs in a list of CodecFormatPair, which we // will then sort so that we can try the best ones first: final List codecFormatPairs = new ArrayList(); // of CodecFormatPair for (int j = 0; j < codecs.size(); ++j) { final String codecClassName = (String) codecs.get(j); if (TRACE) logger.finer(indent(depth) + "Trying " + codecClassName); final Codec codec = (Codec) instantiate(codecClassName); if (codec == null) continue; // TODO: should we call setInputFormat here, or below? (as we are doing now)// final Format fAccepted = codec.setInputFormat(f);// // if (fAccepted == null)// { logger.warning("Codec " + codec + " rejected input format " + f);// continue;// } final Format[] codecOutputFormats = codec.getSupportedOutputFormats(f); for (int codecOutputFormatIndex = 0; codecOutputFormatIndex < codecOutputFormats.length; ++codecOutputFormatIndex) { final Format codecOutputFormat = codecOutputFormats[codecOutputFormatIndex]; if (TRACE) logger.finest(indent(depth) + "Found Codec output format: " + codecOutputFormat); } for (int codecOutputFormatIndex = 0; codecOutputFormatIndex < codecOutputFormats.length; ++codecOutputFormatIndex) { final Format codecOutputFormat = codecOutputFormats[codecOutputFormatIndex]; if (codecOutputFormat == null) { logger.finer(indent(depth) + "Skipping null Codec (" + codec.getClass() + ") output format, input format: " + f); continue; } if (codecOutputFormat.equals(f)) { if (TRACE) logger.finest(indent(depth) + (SKIP_NON_FORMAT_CHANGING_CODECS ? "YES " : "NOT ") + "Skipping Codec output format, same as input format: " + codecOutputFormat); if (SKIP_NON_FORMAT_CHANGING_CODECS) continue; // no need to have a codec that does not change formats. this can happen in the case of // something like com.sun.media.codec.audio.rc.RateCvrt, which will offer an output format // the same as an input format. } // instantiate a new copy of the codec for each pair. codecFormatPairs.add(new CodecFormatPair((Codec) instantiate(codecClassName), codecOutputFormat)); } } // now that we have all of the codec/format pairs, sort them (if we know what format we are trying to reach) if (muxInputFormat != null) { Collections.sort(codecFormatPairs, new CodecFormatPairProximityComparator(muxInputFormat)); // for (int j = 0; j < codecFormatPairs.size(); ++j)// {// final CodecFormatPair codecFormatPair = (CodecFormatPair) codecFormatPairs.get(j);// final Codec codec =codecFormatPair.getCodec();// final Format codecOutputFormat = codecFormatPair.getFormat();// // if (TRACE) logger.fine(indent(depth) + j + ". Will try " + codec.getClass().getName() + " with output format: " + codecOutputFormat);// // } // best-first enabled. if (maxBestCodecs > 1) { while (codecFormatPairs.size() > maxBestCodecs) codecFormatPairs.remove(maxBestCodecs); } for (int j = 0; j < codecFormatPairs.size(); ++j) { final CodecFormatPair codecFormatPair = (CodecFormatPair) codecFormatPairs.get(j); final Codec codec =codecFormatPair.getCodec(); final Format codecOutputFormat = codecFormatPair.getFormat(); if (TRACE) logger.finer(indent(depth) + j + ". Will try " + codec.getClass().getName() + " with output format: " + codecOutputFormat); } } for (int j = 0; j < codecFormatPairs.size(); ++j) { final CodecFormatPair codecFormatPair = (CodecFormatPair) codecFormatPairs.get(j); final Codec codec =codecFormatPair.getCodec(); final Format codecOutputFormat = codecFormatPair.getFormat(); if (TRACE) logger.finer(indent(depth) + "Trying " + codec.getClass().getName() + " with output format: " + codecOutputFormat); { final Format fAccepted = codec.setInputFormat(f); if (fAccepted == null) { logger.warning("Codec " + codec + " rejected input format " + f); continue; } // TODO: the output format may be partially unspecified, so it has to be negotiated. // also, if we are connecting to something with a more specific format (say, a mux), then // we need to be able to constrain this output format using that. final Format codecOutputFormatAccepted = codec.setOutputFormat(codecOutputFormat); // TODO: this should be set only in negotiation if (codecOutputFormatAccepted == null) { logger.warning("Codec " + codec + " rejected output format " + codecOutputFormat); continue; } if (false && codecOutputFormatAccepted.equals(f)) { if (TRACE) logger.finer(indent(depth) + (SKIP_NON_FORMAT_CHANGING_CODECS ? "" : "NOT ") + "Skipping accepted Codec output format, same as input format: " + codecOutputFormatAccepted); if (SKIP_NON_FORMAT_CHANGING_CODECS) continue; // no need to have a codec that does not change formats. this can happen in the case of // something like com.sun.media.codec.audio.rc.RateCvrt, which will offer an output format // the same as an input format. } if (TRACE) logger.finer(indent(depth) + "ACCEPT " + codecOutputFormatAccepted); // for any depth between this one and the cutoff depth, we no longer need to search. for (int i = depth ; i <= cutoffDepth; ++i) { excludeFormatAtDepths.add(new FormatAtDepth(f, i)); } final FilterGraphNode tail = findCodecPathTo(destPlugInType, codecOutputFormatAccepted, codec, excludeFormatAtDepths, mux, muxInputFormat, muxDestTrack, depth + 1, cutoffDepth, maxBestCodecs); if (tail != null) { final CodecNode codecNode = new CodecNode(codec, fAccepted); codecNode.addDestLink(new FilterGraphLink(tail, muxDestTrack)); return codecNode; } } } return null; } /** A combination of Format and depth, used to keep track of where we have already searched. */ private static class FormatAtDepth { private final Format f; private final int depth; public FormatAtDepth(final Format f, final int depth) { super(); this.f = f; this.depth = depth; } public boolean equals(Object obj) { if (!(obj instanceof FormatAtDepth)) return false; FormatAtDepth oCast = (FormatAtDepth) obj; return oCast.depth == this.depth && ObjUtils.equal(oCast.f, this.f); } public int hashCode() { // TODO Auto-generated method stub int result = depth; if (f != null) result += f.toString().hashCode(); // Format.hashCode is not implemented in JMF, so let's not call it. return result; } } // does not check any deeper than demux node. public static Demultiplexer getSourceCompatibleDemultiplexer(DataSource source) { if (TRACE) logger.fine("Content type: " + source.getContentType()); // loop through demuxs for content type: final ContentDescriptor contentDescriptor = new ContentDescriptor(source.getContentType()); final Vector demuxs = PlugInManager.getPlugInList(contentDescriptor, null, PlugInManager.DEMULTIPLEXER); if (TRACE) logger.fine("Num demux: " + demuxs.size()); for (int i = 0; i < demuxs.size(); ++i) { final String demuxClassName = (String) demuxs.get(i); if (TRACE) logger.fine("Demux class name found: " + demuxClassName); final Demultiplexer demux = (Demultiplexer) instantiate(demuxClassName); if (demux == null) continue; try { demux.setSource(source); } catch (IncompatibleSourceException e) { logger.warning("Skipping demux " + demuxClassName + ": " + e.getMessage()); continue; } catch (IOException e) { logger.warning("Skipping demux " + demuxClassName + ": " + e.getMessage()); continue; } return demux; } return null; }// public static DemuxNode buildGraph(DataSource source)// {// final Demultiplexer demux = getSourceCompatibleDemultiplexer(source);// if (demux == null)// return null;// return buildGraph(new ContentDescriptor(source.getContentType()), demux);// // } public static DemuxNode buildGraphToRenderer(ContentDescriptor contentDescriptor, Demultiplexer demux) { return buildGraphTo(PlugInManager.RENDERER, contentDescriptor, demux, null, null, -1); } public static DemuxNode buildGraphToMux(ContentDescriptor contentDescriptor, Demultiplexer demux, final Multiplexer mux, final Format muxInputFormat, final int muxDestTrack) { return buildGraphTo(PlugInManager.MULTIPLEXER, contentDescriptor, demux, mux, muxInputFormat, muxDestTrack); } // TODO: throw exception instead of returning null; /** demux should be opened and started before calling */ private static DemuxNode buildGraphTo(int pluginType, ContentDescriptor contentDescriptor, Demultiplexer demux, final Multiplexer mux, final Format muxInputFormat, final int muxDestTrack) { final Track[] tracks; try { tracks = demux.getTracks(); } catch (BadHeaderException e) { logger.log(Level.WARNING, "" + e, e); return null; } catch (IOException e) { logger.log(Level.WARNING, "" + e, e); return null; } if (tracks == null) { logger.warning("demux " + demux + ": " + "no tracks"); return null; } final DemuxNode demuxNode = new DemuxNode(contentDescriptor, demux, tracks); if (TRACE) logger.fine("Number of tracks: " + demuxNode.getTracks().length); int tracksComplete = 0; for (int trackIndex = 0; trackIndex < demuxNode.getTracks().length; ++trackIndex) { final Track t = demuxNode.getTracks()[trackIndex]; if (TRACE) logger.fine("Track format: " + t.getFormat());// if (!(t.getFormat() instanceof VideoFormat))// { demuxNode.addDest(null);// if (TRACE) logger.fine("Skipping non-video track");// continue; // only video for now.// } // now we have to find a path to the renderer. final FilterGraphNode n = findCodecPathTo(pluginType, t.getFormat(), demux, mux, muxInputFormat, muxDestTrack, 0); // TODO: specify which track it is from? if (n == null) { demuxNode.addDestLink(null); continue; } demuxNode.addDestLink(new FilterGraphLink(n, muxDestTrack)); ++tracksComplete; } if (tracksComplete > 0) { return demuxNode; // TODO: we really only want to return true if ALL tracks are complete. } return null; } private static Object instantiate(String className) { final Class clazz; try { clazz = Class.forName(className); } catch (ClassNotFoundException e) { logger.warning("Unable to instantiate " + className + ": " + e.getMessage()); return null; } try { return clazz.newInstance(); } catch (InstantiationException e) { logger.warning("Unable to instantiate " + className + ": " + e.getMessage()); return null; } catch (IllegalAccessException e) { logger.warning("Unable to instantiate " + className + ": " + e.getMessage()); return null; } catch (Throwable e) { logger.log(Level.SEVERE, "Unable to instantiate " + className + ": " + e.getMessage(), e); return null; } } }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -