Initial commit
[fillmore] / src / marina / singledecodebin.vala
1 /* Ported to Vala from singledecodebin.py in Pitivi:
2  *
3  * Copyright (c) 2005, Edward Hervey <bilboed@bilboed.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 using Gee;
22 using Logging;
23
24 extern void qsort(void *p, size_t num, size_t size, GLib.CompareFunc func);
25
26 // Single-stream queue-less decodebin
27
28 // returns true if the caps are RAW
29 bool is_raw(Gst.Caps caps) {
30     string rep = caps.to_string();
31     string[] valid = { "video/x-raw", "audio/x-raw", "text/plain", "text/x-pango-markup" };
32     foreach (string val in valid)
33         if (rep.has_prefix(val))
34             return true;
35     return false;
36 }
37
38 // A variant of decodebin.
39 // Only outputs one stream; doesn't contain any internal queue.
40 class SingleDecodeBin : Gst.Bin {
41     Gst.Caps caps;
42     Gst.Element typefind;
43     Gst.GhostPad srcpad;
44     
45     ArrayList<Gst.Element> dynamics = new ArrayList<Gst.Element>();
46     ArrayList<Gst.Element> validelements = new ArrayList<Gst.Element>();   // added elements
47     Gst.ElementFactory[] factories;
48
49     const int64 QUEUE_SIZE = 1 * Gst.SECOND;
50   
51     class construct {
52         Gst.PadTemplate sink_pad = new Gst.PadTemplate("sinkpadtemplate", Gst.PadDirection.SINK, 
53                                                         Gst.PadPresence.ALWAYS, new Gst.Caps.any());
54         Gst.PadTemplate src_pad = new Gst.PadTemplate("srcpadtemplate", Gst.PadDirection.SINK, 
55                                                         Gst.PadPresence.ALWAYS, new Gst.Caps.any());
56
57         add_pad_template (src_pad);
58         add_pad_template (sink_pad);
59     }
60
61     public SingleDecodeBin(Gst.Caps? caps, string name, string? uri) throws Error {
62         this.caps = caps == null ? new Gst.Caps.any() : caps;
63
64         typefind = Gst.ElementFactory.make("typefind", "internal-typefind");
65         add(typefind);
66
67         if (uri != null) {
68             Gst.Element file_src = make_element("filesrc");
69
70             file_src.set("location", uri);
71             add(file_src);
72
73             file_src.link(typefind);
74         } else {
75             Gst.GhostPad sinkpad = new Gst.GhostPad("sink", typefind.get_pad("sink"));
76             sinkpad.set_active(true);
77             add_pad(sinkpad);
78         }
79         Signal.connect(typefind, "have-type", (Callback) typefindHaveTypeCb, this);
80
81         factories = getSortedFactoryList();
82     }
83
84     // internal methods
85
86     void controlDynamicElement(Gst.Element element) {
87         dynamics.add(element);
88         element.pad_added.connect(dynamicPadAddedCb);
89         element.no_more_pads.connect(dynamicNoMorePadsCb);
90     }
91
92     static int factory_compare(Gst.ElementFactory** a, Gst.ElementFactory** b) {
93         Gst.PluginFeature* p1 = *(Gst.PluginFeature**)a;
94         Gst.PluginFeature* p2 = *(Gst.PluginFeature**)b;
95         return (int) (((Gst.PluginFeature) p2).get_rank() - ((Gst.PluginFeature) p1).get_rank());
96     }
97
98     // Returns the list of demuxers, decoders and parsers available, sorted by rank
99     Gst.ElementFactory[] getSortedFactoryList() {
100         Gst.Registry registry = Gst.Registry.get_default();
101         Gst.ElementFactory[] factories = new Gst.ElementFactory[0];
102
103         foreach (Gst.PluginFeature plugin_feature in 
104             registry.get_feature_list(typeof(Gst.ElementFactory)) ) {
105
106             Gst.ElementFactory factory = plugin_feature as Gst.ElementFactory;
107             if (factory == null || factory.get_rank() < 64)
108                 continue;
109             string klass = factory.get_klass();
110             if (klass.contains("Demuxer") || klass.contains("Decoder") || klass.contains("Parse"))
111                 factories += factory;
112         }
113
114         qsort(factories, factories.length, sizeof(Gst.ElementFactory *), 
115             (GLib.CompareFunc) factory_compare);
116         return factories;
117     }
118
119     /* Returns a list of factories (sorted by rank) which can take caps as
120      * input. Returns empty list if none are compatible. */
121     Gee.ArrayList<Gst.ElementFactory> findCompatibleFactory(Gst.Caps caps) {
122         Gee.ArrayList<Gst.ElementFactory> res = new Gee.ArrayList<Gst.ElementFactory>();
123
124         foreach (Gst.ElementFactory factory in factories) {
125             weak GLib.List<Gst.StaticPadTemplate?> templates = factory.get_static_pad_templates();
126             foreach (Gst.StaticPadTemplate template in templates)
127                 if (template.direction == Gst.PadDirection.SINK) {
128                     Gst.Caps intersect = caps.intersect(template.static_caps.get());
129                     if (!intersect.is_empty()) {
130                         res.add(factory);
131                         break;
132                     }
133                 }
134         }
135
136         return res;
137     }
138
139     // Inspect element and try to connect something on the srcpads.
140     // If there are dynamic pads, set up a signal handler to
141     // continue autoplugging when they become available.
142     void closeLink(Gst.Element element) {
143         Gst.Pad[] to_connect = new Gst.Pad[0];
144         bool is_dynamic = false;
145
146         foreach (Gst.PadTemplate template in element.get_pad_template_list ()) {
147             if (template.direction != Gst.PadDirection.SRC)
148                 continue;
149             if (template.presence == Gst.PadPresence.ALWAYS) {
150                 Gst.Pad pad = element.get_pad(template.name_template);
151                 to_connect += pad;
152             } else if (template.presence == Gst.PadPresence.SOMETIMES) {
153                 Gst.Pad pad = element.get_pad(template.name_template);
154                 if (pad != null)
155                     to_connect += pad;
156                 else is_dynamic = true;
157             }
158         }
159
160         if (is_dynamic) {
161             emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE,
162                 "%s is a dynamic element".printf(element.get_name()));
163             controlDynamicElement(element);
164         }
165
166         foreach (Gst.Pad pad in to_connect)
167             closePadLink(element, pad, pad.get_caps());
168     }
169
170     bool isDemuxer(Gst.Element element) {
171         if (!element.get_factory().get_klass().contains("Demux"))
172             return false;
173
174         int potential_src_pads = 0;
175         foreach (Gst.PadTemplate template in element.get_pad_template_list()) {
176             if (template.direction != Gst.PadDirection.SRC)
177                 continue;
178
179             if (template.presence == Gst.PadPresence.REQUEST ||
180                 template.name_template.contains("%")) {
181                 potential_src_pads += 2;
182                 break;
183             } else potential_src_pads += 1;
184         }
185
186         return potential_src_pads > 1;
187     }
188
189     Gst.Pad plugDecodingQueue(Gst.Pad pad) {
190         Gst.Element queue = Gst.ElementFactory.make("queue", null);
191         queue.set_property("max_size_time", QUEUE_SIZE);
192         add(queue);
193         queue.sync_state_with_parent();
194         pad.link(queue.get_pad("sink"));
195         return queue.get_pad("src");
196     }
197
198     // Tries to link one of the factories' element to the given pad.
199     // Returns the element that was successfully linked to the pad.
200     Gst.Element tryToLink1(Gst.Element source, Gst.Pad in_pad, 
201         Gee.ArrayList<Gst.ElementFactory> factories) {
202         Gst.Pad? pad = in_pad;
203         if (isDemuxer(source))
204             pad = plugDecodingQueue(in_pad);
205
206         Gst.Element result = null;
207         foreach (Gst.ElementFactory factory in factories) {
208             Gst.Element element = factory.create(null);
209             if (element == null) {
210                 warning("couldn't create element from factory");
211                 continue;
212             }
213
214             Gst.Pad sinkpad = element.get_pad("sink");
215             if (sinkpad == null)
216                 continue;
217
218             add(element);
219             element.set_state(Gst.State.READY);
220             if (pad.link(sinkpad) != Gst.PadLinkReturn.OK) {
221                 element.set_state(Gst.State.NULL);
222                 remove(element);
223                 continue;
224             }
225
226             closeLink(element);
227             element.set_state(Gst.State.PAUSED);
228
229             result = element;
230             break;
231         }
232
233         return result;
234     }
235
236     // Finds the list of elements that could connect to the pad.
237     // If the pad has the desired caps, it will create a ghostpad.
238     // If no compatible elements could be found, the search will stop.
239     void closePadLink(Gst.Element element, Gst.Pad pad, Gst.Caps caps) {
240         emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, 
241             "element:%s, pad:%s, caps:%s".printf(element.get_name(),
242                 pad.get_name(),
243                 caps.to_string()));
244         if (caps.is_empty()) {
245             emit(this, Facility.SINGLEDECODEBIN, Level.INFO, "unknown type");
246             return;
247         }
248         if (caps.is_any()) {
249             emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, "type is not known yet, waiting");
250             return;
251         }
252
253         pad.get_direction ();
254
255         if (!caps.intersect(this.caps).is_empty()) {
256             // This is the desired caps
257             if (srcpad == null)
258                 wrapUp(element, pad);
259         } else if (is_raw(caps)) {
260             emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, 
261                 "We hit a raw caps which isn't the wanted one");
262             // TODO : recursively remove everything until demux/typefind
263         } else {
264             // Find something
265             if (caps.get_size() > 1) {
266                 emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, 
267                     "many possible types, delaying");
268                 return;
269             }
270             Gee.ArrayList<Gst.ElementFactory> facts = findCompatibleFactory(caps);
271             if (facts.size == 0) {
272                 emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, 
273                     "unknown type");
274                 return;
275             }
276             tryToLink1(element, pad, facts);
277        }
278     }
279
280     // Ghost the given pad of element.
281     // Remove non-used elements.
282     void wrapUp(Gst.Element element, Gst.Pad pad) {
283         if (srcpad != null)
284             return;
285         markValidElements(element);
286         removeUnusedElements(typefind);
287         emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, 
288             "ghosting pad %s".printf(pad.get_name()));
289         srcpad = new Gst.GhostPad("src", pad);
290         if (caps.is_fixed()) {
291             srcpad.set_caps(caps);
292         }
293         srcpad.set_active(true);
294         
295         add_pad(srcpad);
296         post_message(new Gst.Message.state_dirty(this));
297     }
298
299     // Mark this element and upstreams as valid
300     void markValidElements(Gst.Element element) {
301         emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, 
302             "element:%s".printf(element.get_name()));
303         if (element == typefind)
304             return;
305         validelements.add(element);
306
307         // find upstream element
308         Gst.Pad pad = (Gst.Pad) element.sinkpads.first().data;
309         Gst.Element parent = pad.get_peer().get_parent_element();
310         markValidElements(parent);
311     }
312
313     //  Remove unused elements connected to srcpad(s) of element
314     void removeUnusedElements(Gst.Element element) {
315         foreach (Gst.Pad pad in element.srcpads)
316             if (pad.is_linked()) {
317                 Gst.Element peer = pad.get_peer().get_parent_element();
318                 removeUnusedElements(peer);
319                 if (!(peer in validelements)) {
320                     emit(this, Facility.SINGLEDECODEBIN, Level.VERBOSE, 
321                         "removing %s".printf(peer.get_name()));
322                     pad.unlink(pad.get_peer());
323                     peer.set_state(Gst.State.NULL);
324                     remove(peer);
325                 }
326             }
327     }
328
329     void cleanUp() {
330         if (srcpad != null)
331             remove_pad(srcpad);
332         srcpad = null;
333         foreach (Gst.Element element in validelements) {
334             element.set_state(Gst.State.NULL);
335             remove(element);
336         }
337         validelements = new Gee.ArrayList<Gst.Element>();
338     }
339
340     // Overrides
341
342     public override Gst.StateChangeReturn change_state(Gst.StateChange transition) {
343         Gst.StateChangeReturn res = base.change_state(transition);
344         if (transition == Gst.StateChange.PAUSED_TO_READY)
345             cleanUp();
346         return res;
347     }
348
349     // Signal callbacks
350
351     static void typefindHaveTypeCb(Gst.Element t, int probability, Gst.Caps caps, 
352                                                                             SingleDecodeBin data) {
353         emit(data, Facility.SINGLEDECODEBIN, Level.VERBOSE, 
354             "probability:%d, caps:%s".printf(probability, caps.to_string()));
355         data.closePadLink(t, t.get_pad("src"), caps);
356     }
357
358     // Dynamic element Callbacks
359
360     void dynamicPadAddedCb(Gst.Element element, Gst.Pad pad) {
361         emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "dynamicPadAddedCb");
362         if (srcpad == null)
363             closePadLink(element, pad, pad.get_caps());
364     }
365
366     void dynamicNoMorePadsCb(Gst.Element element) {
367         emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "dynamicNoMorePadsCb");
368     }
369 }
370