/* Ported to Vala from singledecodebin.py in Pitivi: * * Copyright (c) 2005, Edward Hervey * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ using Gee; using Logging; extern void qsort(void *p, size_t num, size_t size, GLib.CompareFunc func); // Single-stream queue-less decodebin // returns true if the caps are RAW bool is_raw(Gst.Caps caps) { string rep = caps.to_string(); string[] valid = { "video/x-raw", "audio/x-raw", "text/plain", "text/x-pango-markup" }; foreach (string val in valid) if (rep.has_prefix(val)) return true; return false; } // A variant of decodebin. // Only outputs one stream; doesn't contain any internal queue. class SingleDecodeBin : Gst.Bin { Gst.Caps caps; Gst.Element typefind; Gst.GhostPad srcpad; ArrayList dynamics = new ArrayList(); ArrayList validelements = new ArrayList(); // added elements Gst.ElementFactory[] factories; const int64 QUEUE_SIZE = 1 * Gst.SECOND; class construct { Gst.PadTemplate sink_pad = new Gst.PadTemplate("sinkpadtemplate", Gst.PadDirection.SINK, Gst.PadPresence.ALWAYS, new Gst.Caps.any()); Gst.PadTemplate src_pad = new Gst.PadTemplate("srcpadtemplate", Gst.PadDirection.SINK, Gst.PadPresence.ALWAYS, new Gst.Caps.any()); add_pad_template (src_pad); add_pad_template (sink_pad); } public SingleDecodeBin(Gst.Caps? caps, string name, string? uri) throws Error { this.caps = caps == null ? new Gst.Caps.any() : caps; typefind = Gst.ElementFactory.make("typefind", "internal-typefind"); add(typefind); if (uri != null) { Gst.Element file_src = make_element("filesrc"); file_src.set("location", uri); add(file_src); file_src.link(typefind); } else { Gst.GhostPad sinkpad = new Gst.GhostPad("sink", typefind.get_pad("sink")); sinkpad.set_active(true); add_pad(sinkpad); } Signal.connect(typefind, "have-type", (Callback) typefindHaveTypeCb, this); factories = getSortedFactoryList(); } // internal methods void controlDynamicElement(Gst.Element element) { dynamics.add(element); element.pad_added.connect(dynamicPadAddedCb); element.no_more_pads.connect(dynamicNoMorePadsCb); } static int factory_compare(Gst.ElementFactory** a, Gst.ElementFactory** b) { Gst.PluginFeature* p1 = *(Gst.PluginFeature**)a; Gst.PluginFeature* p2 = *(Gst.PluginFeature**)b; return (int) (((Gst.PluginFeature) p2).get_rank() - ((Gst.PluginFeature) p1).get_rank()); } // Returns the list of demuxers, decoders and parsers available, sorted by rank Gst.ElementFactory[] getSortedFactoryList() { Gst.Registry registry = Gst.Registry.get_default(); Gst.ElementFactory[] factories = new Gst.ElementFactory[0]; foreach (Gst.PluginFeature plugin_feature in registry.get_feature_list(typeof(Gst.ElementFactory)) ) { Gst.ElementFactory factory = plugin_feature as Gst.ElementFactory; if (factory == null || factory.get_rank() < 64) continue; string klass = factory.get_klass(); if (klass.contains("Demuxer") || klass.contains("Decoder") || klass.contains("Parse")) factories += factory; } qsort(factories, factories.length, sizeof(Gst.ElementFactory *), (GLib.CompareFunc) factory_compare); return factories; } /* Returns a list of factories (sorted by rank) which can take caps as * input. Returns empty list if none are compatible. */ Gee.ArrayList findCompatibleFactory(Gst.Caps caps) { Gee.ArrayList res = new Gee.ArrayList(); foreach (Gst.ElementFactory factory in factories) { weak GLib.List templates = factory.get_static_pad_templates(); foreach (Gst.StaticPadTemplate template in templates) if (template.direction == Gst.PadDirection.SINK) { Gst.Caps intersect = caps.intersect(template.static_caps.get()); if (!intersect.is_empty()) { res.add(factory); break; } } } return res; } // Inspect element and try to connect something on the srcpads. // If there are dynamic pads, set up a signal handler to // continue autoplugging when they become available. void closeLink(Gst.Element element) { Gst.Pad[] to_connect = new Gst.Pad[0]; bool is_dynamic = false; foreach (Gst.PadTemplate template in element.get_pad_template_list ()) { if (template.direction != Gst.PadDirection.SRC) continue; if (template.presence == Gst.PadPresence.ALWAYS) { Gst.Pad pad = element.get_pad(template.name_template); to_connect += pad; } else if (template.presence == Gst.PadPresence.SOMETIMES) { Gst.Pad pad = element.get_pad(template.name_template); if (pad != null) to_connect += pad; else is_dynamic = true; } } if (is_dynamic) { emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, "%s is a dynamic element".printf(element.get_name())); controlDynamicElement(element); } foreach (Gst.Pad pad in to_connect) closePadLink(element, pad, pad.get_caps()); } bool isDemuxer(Gst.Element element) { if (!element.get_factory().get_klass().contains("Demux")) return false; int potential_src_pads = 0; foreach (Gst.PadTemplate template in element.get_pad_template_list()) { if (template.direction != Gst.PadDirection.SRC) continue; if (template.presence == Gst.PadPresence.REQUEST || template.name_template.contains("%")) { potential_src_pads += 2; break; } else potential_src_pads += 1; } return potential_src_pads > 1; } Gst.Pad plugDecodingQueue(Gst.Pad pad) { Gst.Element queue = Gst.ElementFactory.make("queue", null); queue.set_property("max_size_time", QUEUE_SIZE); add(queue); queue.sync_state_with_parent(); pad.link(queue.get_pad("sink")); return queue.get_pad("src"); } // Tries to link one of the factories' element to the given pad. // Returns the element that was successfully linked to the pad. Gst.Element tryToLink1(Gst.Element source, Gst.Pad in_pad, Gee.ArrayList factories) { Gst.Pad? pad = in_pad; if (isDemuxer(source)) pad = plugDecodingQueue(in_pad); Gst.Element result = null; foreach (Gst.ElementFactory factory in factories) { Gst.Element element = factory.create(null); if (element == null) { warning("couldn't create element from factory"); continue; } Gst.Pad sinkpad = element.get_pad("sink"); if (sinkpad == null) continue; add(element); element.set_state(Gst.State.READY); if (pad.link(sinkpad) != Gst.PadLinkReturn.OK) { element.set_state(Gst.State.NULL); remove(element); continue; } closeLink(element); element.set_state(Gst.State.PAUSED); result = element; break; } return result; } // Finds the list of elements that could connect to the pad. // If the pad has the desired caps, it will create a ghostpad. // If no compatible elements could be found, the search will stop. void closePadLink(Gst.Element element, Gst.Pad pad, Gst.Caps caps) { emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, "element:%s, pad:%s, caps:%s".printf(element.get_name(), pad.get_name(), caps.to_string())); if (caps.is_empty()) { emit(this, Facility.SINGLEDECODEBIN, Level.INFO, "unknown type"); return; } if (caps.is_any()) { emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, "type is not known yet, waiting"); return; } pad.get_direction (); if (!caps.intersect(this.caps).is_empty()) { // This is the desired caps if (srcpad == null) wrapUp(element, pad); } else if (is_raw(caps)) { emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, "We hit a raw caps which isn't the wanted one"); // TODO : recursively remove everything until demux/typefind } else { // Find something if (caps.get_size() > 1) { emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, "many possible types, delaying"); return; } Gee.ArrayList facts = findCompatibleFactory(caps); if (facts.size == 0) { emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, "unknown type"); return; } tryToLink1(element, pad, facts); } } // Ghost the given pad of element. // Remove non-used elements. void wrapUp(Gst.Element element, Gst.Pad pad) { if (srcpad != null) return; markValidElements(element); removeUnusedElements(typefind); emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, "ghosting pad %s".printf(pad.get_name())); srcpad = new Gst.GhostPad("src", pad); if (caps.is_fixed()) { srcpad.set_caps(caps); } srcpad.set_active(true); add_pad(srcpad); post_message(new Gst.Message.state_dirty(this)); } // Mark this element and upstreams as valid void markValidElements(Gst.Element element) { emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, "element:%s".printf(element.get_name())); if (element == typefind) return; validelements.add(element); // find upstream element Gst.Pad pad = (Gst.Pad) element.sinkpads.first().data; Gst.Element parent = pad.get_peer().get_parent_element(); markValidElements(parent); } // Remove unused elements connected to srcpad(s) of element void removeUnusedElements(Gst.Element element) { foreach (Gst.Pad pad in element.srcpads) if (pad.is_linked()) { Gst.Element peer = pad.get_peer().get_parent_element(); removeUnusedElements(peer); if (!(peer in validelements)) { emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, "removing %s".printf(peer.get_name())); pad.unlink(pad.get_peer()); peer.set_state(Gst.State.NULL); remove(peer); } } } void cleanUp() { if (srcpad != null) remove_pad(srcpad); srcpad = null; foreach (Gst.Element element in validelements) { element.set_state(Gst.State.NULL); remove(element); } validelements = new Gee.ArrayList(); } // Overrides public override Gst.StateChangeReturn change_state(Gst.StateChange transition) { Gst.StateChangeReturn res = base.change_state(transition); if (transition == Gst.StateChange.PAUSED_TO_READY) cleanUp(); return res; } // Signal callbacks static void typefindHaveTypeCb(Gst.Element t, int probability, Gst.Caps caps, SingleDecodeBin data) { emit(data, Facility.SINGLEDECODEBIN, Level.VERBOSE, "probability:%d, caps:%s".printf(probability, caps.to_string())); data.closePadLink(t, t.get_pad("src"), caps); } // Dynamic element Callbacks void dynamicPadAddedCb(Gst.Element element, Gst.Pad pad) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "dynamicPadAddedCb"); if (srcpad == null) closePadLink(element, pad, pad.get_caps()); } void dynamicNoMorePadsCb(Gst.Element element) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "dynamicNoMorePadsCb"); } }