|
1 /* GStreamer |
|
2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> |
|
3 * |
|
4 * This library is free software; you can redistribute it and/or |
|
5 * modify it under the terms of the GNU Library General Public |
|
6 * License as published by the Free Software Foundation; either |
|
7 * version 2 of the License, or (at your option) any later version. |
|
8 * |
|
9 * This library is distributed in the hope that it will be useful, |
|
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
12 * Library General Public License for more details. |
|
13 * |
|
14 * You should have received a copy of the GNU Library General Public |
|
15 * License along with this library; if not, write to the |
|
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
|
17 * Boston, MA 02111-1307, USA. |
|
18 */ |
|
19 |
|
20 /** |
|
21 * SECTION:element-playbin |
|
22 * |
|
23 * <refsect2> |
|
24 * <para> |
|
25 * Playbin provides a stand-alone everything-in-one abstraction for an |
|
26 * audio and/or video player. |
|
27 * </para> |
|
28 * <para> |
|
29 * It can handle both audio and video files and features |
|
30 * <itemizedlist> |
|
31 * <listitem> |
|
32 * automatic file type recognition and based on that automatic |
|
33 * selection and usage of the right audio/video/subtitle demuxers/decoders |
|
34 * </listitem> |
|
35 * <listitem> |
|
36 * visualisations for audio files |
|
37 * </listitem> |
|
38 * <listitem> |
|
39 * subtitle support for video files |
|
40 * </listitem> |
|
41 * <listitem> |
|
42 * stream selection between different audio/subtitles streams |
|
43 * </listitem> |
|
44 * <listitem> |
|
45 * meta info (tag) extraction |
|
46 * </listitem> |
|
47 * <listitem> |
|
48 * easy access to the last video frame |
|
49 * </listitem> |
|
50 * <listitem> |
|
51 * buffering when playing streams over a network |
|
52 * </listitem> |
|
53 * <listitem> |
|
54 * volume control |
|
55 * </listitem> |
|
56 * </itemizedlist> |
|
57 * </para> |
|
58 * <title>Usage</title> |
|
59 * <para> |
|
60 * A playbin element can be created just like any other element using |
|
61 * gst_element_factory_make(). The file/URI to play should be set via the "uri" |
|
62 * property. This must be an absolute URI, relative file paths are not allowed. |
|
63 * Example URIs are file:///home/joe/movie.avi or http://www.joedoe.com/foo.ogg |
|
64 * </para> |
|
65 * <para> |
|
66 * Playbin is a #GstPipeline. It will notify the application of everything |
|
67 * that's happening (errors, end of stream, tags found, state changes, etc.) |
|
68 * by posting messages on its #GstBus. The application needs to watch the |
|
69 * bus. |
|
70 * </para> |
|
71 * <para> |
|
72 * Playback can be initiated by setting the element to PLAYING state using |
|
73 * gst_element_set_state(). Note that the state change will take place in |
|
74 * the background in a separate thread, when the function returns playback |
|
75 * is probably not happening yet and any errors might not have occured yet. |
|
76 * Applications using playbin should ideally be written to deal with things |
|
77 * completely asynchroneous. |
|
78 * </para> |
|
79 * <para> |
|
80 * When playback has finished (an EOS message has been received on the bus) |
|
81 * or an error has occured (an ERROR message has been received on the bus) or |
|
82 * the user wants to play a different track, playbin should be set back to |
|
83 * READY or NULL state, then the "uri" property should be set to the new |
|
84 * location and then playbin be set to PLAYING state again. |
|
85 * </para> |
|
86 * <para> |
|
87 * Seeking can be done using gst_element_seek_simple() or gst_element_seek() |
|
88 * on the playbin element. Again, the seek will not be executed |
|
89 * instantaneously, but will be done in a background thread. When the seek |
|
90 * call returns the seek will most likely still be in process. An application |
|
91 * may wait for the seek to finish (or fail) using gst_element_get_state() with |
|
92 * -1 as the timeout, but this will block the user interface and is not |
|
93 * recommended at all. |
|
94 * </para> |
|
95 * <para> |
|
96 * Applications may query the current position and duration of the stream |
|
97 * via gst_element_query_position() and gst_element_query_duration() and |
|
98 * setting the format passed to GST_FORMAT_TIME. If the query was successful, |
|
99 * the duration or position will have been returned in units of nanoseconds. |
|
100 * </para> |
|
101 * <title>Advanced Usage: specifying the audio and video sink</title> |
|
102 * <para> |
|
103 * By default, if no audio sink or video sink has been specified via the |
|
104 * "audio-sink" or "video-sink" property, playbin will use the autoaudiosink |
|
105 * and autovideosink elements to find the first-best available output method. |
|
106 * This should work in most cases, but is not always desirable. Often either |
|
107 * the user or application might want to specify more explicitly what to use |
|
108 * for audio and video output. |
|
109 * </para> |
|
110 * <para> |
|
111 * If the application wants more control over how audio or video should be |
|
112 * output, it may create the audio/video sink elements itself (for example |
|
113 * using gst_element_factory_make()) and provide them to playbin using the |
|
114 * "audio-sink" or "video-sink" property. |
|
115 * </para> |
|
116 * <para> |
|
117 * GNOME-based applications, for example, will usually want to create |
|
118 * gconfaudiosink and gconfvideosink elements and make playbin use those, |
|
119 * so that output happens to whatever the user has configured in the GNOME |
|
120 * Multimedia System Selector confinguration dialog. |
|
121 * </para> |
|
122 * <para> |
|
123 * The sink elements do not necessarily need to be ready-made sinks. It is |
|
124 * possible to create container elements that look like a sink to playbin, |
|
125 * but in reality contain a number of custom elements linked together. This |
|
126 * can be achieved by creating a #GstBin and putting elements in there and |
|
127 * linking them, and then creating a sink #GstGhostPad for the bin and pointing |
|
128 * it to the sink pad of the first element within the bin. This can be used |
|
129 * for a number of purposes, for example to force output to a particular |
|
130 * format or to modify or observe the data before it is output. |
|
131 * </para> |
|
132 * <para> |
|
133 * It is also possible to 'suppress' audio and/or video output by using |
|
134 * 'fakesink' elements (or capture it from there using the fakesink element's |
|
135 * "handoff" signal, which, nota bene, is fired from the streaming thread!). |
|
136 * </para> |
|
137 * <title>Retrieving Tags and Other Meta Data</title> |
|
138 * <para> |
|
139 * Most of the common meta data (artist, title, etc.) can be retrieved by |
|
140 * watching for TAG messages on the pipeline's bus (see above). |
|
141 * </para> |
|
142 * <para> |
|
143 * Other more specific meta information like width/height/framerate of video |
|
144 * streams or samplerate/number of channels of audio streams can be obtained |
|
145 * using the "stream-info" property, which will return a GList of stream info |
|
146 * objects, one for each stream. These are opaque objects that can only be |
|
147 * accessed via the standard GObject property interface, ie. g_object_get(). |
|
148 * Each stream info object has the following properties: |
|
149 * <itemizedlist> |
|
150 * <listitem>"object" (GstObject) (the decoder source pad usually)</listitem> |
|
151 * <listitem>"type" (enum) (if this is an audio/video/subtitle stream)</listitem> |
|
152 * <listitem>"decoder" (string) (name of decoder used to decode this stream)</listitem> |
|
153 * <listitem>"mute" (boolean) (to mute or unmute this stream)</listitem> |
|
154 * <listitem>"caps" (GstCaps) (caps of the decoded stream)</listitem> |
|
155 * <listitem>"language-code" (string) (ISO-639 language code for this stream, mostly used for audio/subtitle streams)</listitem> |
|
156 * <listitem>"codec" (string) (format this stream was encoded in)</listitem> |
|
157 * </itemizedlist> |
|
158 * Stream information from the stream-info properties is best queried once |
|
159 * playbin has changed into PAUSED or PLAYING state (which can be detected |
|
160 * via a state-changed message on the bus where old_state=READY and |
|
161 * new_state=PAUSED), since before that the list might not be complete yet or |
|
162 * not contain all available information (like language-codes). |
|
163 * </para> |
|
164 * <title>Buffering</title> |
|
165 * <para> |
|
166 * Playbin handles buffering automatically for the most part, but applications |
|
167 * need to handle parts of the buffering process as well. Whenever playbin is |
|
168 * buffering, it will post BUFFERING messages on the bus with a percentage |
|
169 * value that shows the progress of the buffering process. Applications need |
|
170 * to set playbin to PLAYING or PAUSED state in response to these messages. |
|
171 * They may also want to convey the buffering progress to the user in some |
|
172 * way. Here is how to extract the percentage information from the message |
|
173 * (requires GStreamer >= 0.10.11): |
|
174 * </para> |
|
175 * <para> |
|
176 * <programlisting> |
|
177 * switch (GST_MESSAGE_TYPE (msg)) { |
|
178 * case GST_MESSAGE_BUFFERING: { |
|
179 * gint percent = 0; |
|
180 * gst_message_parse_buffering (msg, &percent); |
|
181 * g_print ("Buffering (%%u percent done)", percent); |
|
182 * break; |
|
183 * } |
|
184 * ... |
|
185 * } |
|
186 * </programlisting> |
|
187 * Note that applications should keep/set the pipeline in the PAUSED state when |
|
188 * a BUFFERING message is received with a buffer percent value < 100 and set |
|
189 * the pipeline back to PLAYING state when a BUFFERING message with a value |
|
190 * of 100 percent is received (if PLAYING is the desired state, that is). |
|
191 * </para> |
|
192 * <title>Embedding the video window in your application</title> |
|
193 * <para> |
|
194 * By default, playbin (or rather the video sinks used) will create their own |
|
195 * window. Applications will usually want to force output to a window of their |
|
196 * own, however. This can be done using the GstXOverlay interface, which most |
|
197 * video sinks implement. See the documentation there for more details. |
|
198 * </para> |
|
199 * <title>Specifying which CD/DVD device to use</title> |
|
200 * <para> |
|
201 * The device to use for CDs/DVDs needs to be set on the source element |
|
202 * playbin creates before it is opened. The only way to do this at the moment |
|
203 * is to connect to playbin's "notify::source" signal, which will be emitted |
|
204 * by playbin when it has created the source element for a particular URI. |
|
205 * In the signal callback you can check if the source element has a "device" |
|
206 * property and set it appropriately. In future ways might be added to specify |
|
207 * the device as part of the URI, but at the time of writing this is not |
|
208 * possible yet. |
|
209 * </para> |
|
210 * <title>Examples</title> |
|
211 * <para> |
|
212 * Here is a simple pipeline to play back a video or audio file: |
|
213 * <programlisting> |
|
214 * gst-launch -v playbin uri=file:///path/to/somefile.avi |
|
215 * </programlisting> |
|
216 * This will play back the given AVI video file, given that the video and |
|
217 * audio decoders required to decode the content are installed. Since no |
|
218 * special audio sink or video sink is supplied (not possible via gst-launch), |
|
219 * playbin will try to find a suitable audio and video sink automatically |
|
220 * using the autoaudiosink and autovideosink elements. |
|
221 * </para> |
|
222 * <para> |
|
223 * Here is a another pipeline to play track 4 of an audio CD: |
|
224 * <programlisting> |
|
225 * gst-launch -v playbin uri=cdda://4 |
|
226 * </programlisting> |
|
227 * This will play back track 4 on an audio CD in your disc drive (assuming |
|
228 * the drive is detected automatically by the plugin). |
|
229 * </para> |
|
230 * <para> |
|
231 * Here is a another pipeline to play title 1 of a DVD: |
|
232 * <programlisting> |
|
233 * gst-launch -v playbin uri=dvd://1 |
|
234 * </programlisting> |
|
235 * This will play back title 1 of a DVD in your disc drive (assuming |
|
236 * the drive is detected automatically by the plugin). |
|
237 * </para> |
|
238 * </refsect2> |
|
239 */ |
|
240 |
|
241 #ifdef HAVE_CONFIG_H |
|
242 #include "config.h" |
|
243 #endif |
|
244 |
|
245 #include <string.h> |
|
246 #include <gst/gst.h> |
|
247 |
|
248 #include <gst/gst-i18n-plugin.h> |
|
249 #include <gst/pbutils/pbutils.h> |
|
250 |
|
251 #include "gstplaybasebin.h" |
|
252 |
|
253 #ifdef __SYMBIAN32__ |
|
254 #include <glib_global.h> |
|
255 #endif |
|
256 |
|
257 GST_DEBUG_CATEGORY_STATIC (gst_play_bin_debug); |
|
258 #define GST_CAT_DEFAULT gst_play_bin_debug |
|
259 |
|
260 #define GST_TYPE_PLAY_BIN (gst_play_bin_get_type()) |
|
261 #define GST_PLAY_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PLAY_BIN,GstPlayBin)) |
|
262 #define GST_PLAY_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PLAY_BIN,GstPlayBinClass)) |
|
263 #define GST_IS_PLAY_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PLAY_BIN)) |
|
264 #define GST_IS_PLAY_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PLAY_BIN)) |
|
265 |
|
266 #define VOLUME_MAX_DOUBLE 10.0 |
|
267 |
|
268 typedef struct _GstPlayBin GstPlayBin; |
|
269 typedef struct _GstPlayBinClass GstPlayBinClass; |
|
270 |
|
271 struct _GstPlayBin |
|
272 { |
|
273 GstPlayBaseBin parent; |
|
274 |
|
275 /* the configurable elements */ |
|
276 GstElement *fakesink; |
|
277 GstElement *audio_sink; |
|
278 GstElement *video_sink; |
|
279 GstElement *visualisation; |
|
280 GstElement *pending_visualisation; |
|
281 GstElement *volume_element; |
|
282 GstElement *textoverlay_element; |
|
283 gfloat volume; |
|
284 |
|
285 /* these are the currently active sinks */ |
|
286 GList *sinks; |
|
287 |
|
288 /* the last captured frame for snapshots */ |
|
289 GstBuffer *frame; |
|
290 |
|
291 /* our cache for the sinks */ |
|
292 GHashTable *cache; |
|
293 |
|
294 /* font description */ |
|
295 gchar *font_desc; |
|
296 |
|
297 /* indication if the pipeline is live */ |
|
298 gboolean is_live; |
|
299 }; |
|
300 |
|
301 struct _GstPlayBinClass |
|
302 { |
|
303 GstPlayBaseBinClass parent_class; |
|
304 }; |
|
305 |
|
306 /* props */ |
|
307 enum |
|
308 { |
|
309 ARG_0, |
|
310 ARG_AUDIO_SINK, |
|
311 ARG_VIDEO_SINK, |
|
312 ARG_VIS_PLUGIN, |
|
313 ARG_VOLUME, |
|
314 ARG_FRAME, |
|
315 ARG_FONT_DESC |
|
316 }; |
|
317 |
|
318 /* signals */ |
|
319 enum |
|
320 { |
|
321 LAST_SIGNAL |
|
322 }; |
|
323 |
|
324 static void gst_play_bin_class_init (GstPlayBinClass * klass); |
|
325 static void gst_play_bin_init (GstPlayBin * play_bin); |
|
326 static void gst_play_bin_dispose (GObject * object); |
|
327 |
|
328 static gboolean setup_sinks (GstPlayBaseBin * play_base_bin, |
|
329 GstPlayBaseGroup * group); |
|
330 static void remove_sinks (GstPlayBin * play_bin); |
|
331 static void playbin_set_subtitles_visible (GstPlayBaseBin * play_base_bin, |
|
332 gboolean visible); |
|
333 |
|
334 static void gst_play_bin_set_property (GObject * object, guint prop_id, |
|
335 const GValue * value, GParamSpec * spec); |
|
336 static void gst_play_bin_get_property (GObject * object, guint prop_id, |
|
337 GValue * value, GParamSpec * spec); |
|
338 |
|
339 static gboolean gst_play_bin_send_event (GstElement * element, |
|
340 GstEvent * event); |
|
341 static GstStateChangeReturn gst_play_bin_change_state (GstElement * element, |
|
342 GstStateChange transition); |
|
343 |
|
344 static void gst_play_bin_handle_message (GstBin * bin, GstMessage * message); |
|
345 |
|
346 static GstElementClass *parent_class; |
|
347 |
|
348 //static guint gst_play_bin_signals[LAST_SIGNAL] = { 0 }; |
|
349 |
|
350 static const GstElementDetails gst_play_bin_details = |
|
351 GST_ELEMENT_DETAILS ("Player Bin", |
|
352 "Generic/Bin/Player", |
|
353 "Autoplug and play media from an uri", |
|
354 "Wim Taymans <wim.taymans@gmail.com>"); |
|
355 |
|
356 static GType |
|
357 gst_play_bin_get_type (void) |
|
358 { |
|
359 static GType gst_play_bin_type = 0; |
|
360 |
|
361 if (!gst_play_bin_type) { |
|
362 static const GTypeInfo gst_play_bin_info = { |
|
363 sizeof (GstPlayBinClass), |
|
364 NULL, |
|
365 NULL, |
|
366 (GClassInitFunc) gst_play_bin_class_init, |
|
367 NULL, |
|
368 NULL, |
|
369 sizeof (GstPlayBin), |
|
370 0, |
|
371 (GInstanceInitFunc) gst_play_bin_init, |
|
372 NULL |
|
373 }; |
|
374 |
|
375 gst_play_bin_type = g_type_register_static (GST_TYPE_PLAY_BASE_BIN, |
|
376 "GstPlayBin", &gst_play_bin_info, 0); |
|
377 } |
|
378 |
|
379 return gst_play_bin_type; |
|
380 } |
|
381 |
|
382 static void |
|
383 gst_play_bin_class_init (GstPlayBinClass * klass) |
|
384 { |
|
385 GObjectClass *gobject_klass; |
|
386 GstElementClass *gstelement_klass; |
|
387 GstBinClass *gstbin_klass; |
|
388 GstPlayBaseBinClass *playbasebin_klass; |
|
389 |
|
390 gobject_klass = (GObjectClass *) klass; |
|
391 gstelement_klass = (GstElementClass *) klass; |
|
392 gstbin_klass = (GstBinClass *) klass; |
|
393 playbasebin_klass = (GstPlayBaseBinClass *) klass; |
|
394 |
|
395 parent_class = g_type_class_peek_parent (klass); |
|
396 |
|
397 gobject_klass->set_property = gst_play_bin_set_property; |
|
398 gobject_klass->get_property = gst_play_bin_get_property; |
|
399 |
|
400 g_object_class_install_property (gobject_klass, ARG_VIDEO_SINK, |
|
401 g_param_spec_object ("video-sink", "Video Sink", |
|
402 "the video output element to use (NULL = default sink)", |
|
403 GST_TYPE_ELEMENT, G_PARAM_READWRITE)); |
|
404 g_object_class_install_property (gobject_klass, ARG_AUDIO_SINK, |
|
405 g_param_spec_object ("audio-sink", "Audio Sink", |
|
406 "the audio output element to use (NULL = default sink)", |
|
407 GST_TYPE_ELEMENT, G_PARAM_READWRITE)); |
|
408 g_object_class_install_property (gobject_klass, ARG_VIS_PLUGIN, |
|
409 g_param_spec_object ("vis-plugin", "Vis plugin", |
|
410 "the visualization element to use (NULL = none)", |
|
411 GST_TYPE_ELEMENT, G_PARAM_READWRITE)); |
|
412 g_object_class_install_property (gobject_klass, ARG_VOLUME, |
|
413 g_param_spec_double ("volume", "volume", "volume", |
|
414 0.0, VOLUME_MAX_DOUBLE, 1.0, G_PARAM_READWRITE)); |
|
415 g_object_class_install_property (gobject_klass, ARG_FRAME, |
|
416 gst_param_spec_mini_object ("frame", "Frame", |
|
417 "The last frame (NULL = no video available)", |
|
418 GST_TYPE_BUFFER, G_PARAM_READABLE)); |
|
419 g_object_class_install_property (gobject_klass, ARG_FONT_DESC, |
|
420 g_param_spec_string ("subtitle-font-desc", |
|
421 "Subtitle font description", |
|
422 "Pango font description of font " |
|
423 "to be used for subtitle rendering", NULL, G_PARAM_WRITABLE)); |
|
424 |
|
425 gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_play_bin_dispose); |
|
426 |
|
427 gst_element_class_set_details (gstelement_klass, &gst_play_bin_details); |
|
428 |
|
429 gstelement_klass->change_state = |
|
430 GST_DEBUG_FUNCPTR (gst_play_bin_change_state); |
|
431 gstelement_klass->send_event = GST_DEBUG_FUNCPTR (gst_play_bin_send_event); |
|
432 |
|
433 gstbin_klass->handle_message = |
|
434 GST_DEBUG_FUNCPTR (gst_play_bin_handle_message); |
|
435 |
|
436 playbasebin_klass->setup_output_pads = setup_sinks; |
|
437 playbasebin_klass->set_subtitles_visible = playbin_set_subtitles_visible; |
|
438 } |
|
439 |
|
440 static void |
|
441 gst_play_bin_init (GstPlayBin * play_bin) |
|
442 { |
|
443 play_bin->video_sink = NULL; |
|
444 play_bin->audio_sink = NULL; |
|
445 play_bin->visualisation = NULL; |
|
446 play_bin->pending_visualisation = NULL; |
|
447 play_bin->volume_element = NULL; |
|
448 play_bin->textoverlay_element = NULL; |
|
449 play_bin->volume = 1.0; |
|
450 play_bin->sinks = NULL; |
|
451 play_bin->frame = NULL; |
|
452 play_bin->font_desc = NULL; |
|
453 play_bin->cache = g_hash_table_new_full (g_str_hash, g_str_equal, |
|
454 NULL, (GDestroyNotify) gst_object_unref); |
|
455 } |
|
456 |
|
457 static void |
|
458 gst_play_bin_dispose (GObject * object) |
|
459 { |
|
460 GstPlayBin *play_bin; |
|
461 |
|
462 play_bin = GST_PLAY_BIN (object); |
|
463 |
|
464 if (play_bin->cache != NULL) { |
|
465 remove_sinks (play_bin); |
|
466 g_hash_table_destroy (play_bin->cache); |
|
467 play_bin->cache = NULL; |
|
468 } |
|
469 |
|
470 if (play_bin->audio_sink != NULL) { |
|
471 gst_element_set_state (play_bin->audio_sink, GST_STATE_NULL); |
|
472 gst_object_unref (play_bin->audio_sink); |
|
473 play_bin->audio_sink = NULL; |
|
474 } |
|
475 if (play_bin->video_sink != NULL) { |
|
476 gst_element_set_state (play_bin->video_sink, GST_STATE_NULL); |
|
477 gst_object_unref (play_bin->video_sink); |
|
478 play_bin->video_sink = NULL; |
|
479 } |
|
480 if (play_bin->visualisation != NULL) { |
|
481 gst_element_set_state (play_bin->visualisation, GST_STATE_NULL); |
|
482 gst_object_unref (play_bin->visualisation); |
|
483 play_bin->visualisation = NULL; |
|
484 } |
|
485 if (play_bin->pending_visualisation != NULL) { |
|
486 gst_element_set_state (play_bin->pending_visualisation, GST_STATE_NULL); |
|
487 gst_object_unref (play_bin->pending_visualisation); |
|
488 play_bin->pending_visualisation = NULL; |
|
489 } |
|
490 if (play_bin->textoverlay_element != NULL) { |
|
491 gst_object_unref (play_bin->textoverlay_element); |
|
492 play_bin->textoverlay_element = NULL; |
|
493 } |
|
494 g_free (play_bin->font_desc); |
|
495 play_bin->font_desc = NULL; |
|
496 |
|
497 G_OBJECT_CLASS (parent_class)->dispose (object); |
|
498 } |
|
499 |
|
500 static void |
|
501 gst_play_bin_vis_unblocked (GstPad * tee_pad, gboolean blocked, |
|
502 gpointer user_data) |
|
503 { |
|
504 GstPlayBin *play_bin = GST_PLAY_BIN (user_data); |
|
505 |
|
506 if (play_bin->pending_visualisation) |
|
507 gst_pad_set_blocked_async (tee_pad, FALSE, gst_play_bin_vis_unblocked, |
|
508 play_bin); |
|
509 } |
|
510 |
|
511 static void |
|
512 gst_play_bin_vis_blocked (GstPad * tee_pad, gboolean blocked, |
|
513 gpointer user_data) |
|
514 { |
|
515 GstPlayBin *play_bin = GST_PLAY_BIN (user_data); |
|
516 GstBin *vis_bin = NULL; |
|
517 GstPad *vis_sink_pad = NULL, *vis_src_pad = NULL, *vqueue_pad = NULL; |
|
518 GstState bin_state; |
|
519 GstElement *pending_visualisation; |
|
520 |
|
521 GST_OBJECT_LOCK (play_bin); |
|
522 pending_visualisation = play_bin->pending_visualisation; |
|
523 play_bin->pending_visualisation = NULL; |
|
524 GST_OBJECT_UNLOCK (play_bin); |
|
525 |
|
526 /* We want to disable visualisation */ |
|
527 if (!GST_IS_ELEMENT (pending_visualisation)) { |
|
528 /* Set visualisation element to READY */ |
|
529 gst_element_set_state (play_bin->visualisation, GST_STATE_READY); |
|
530 goto beach; |
|
531 } |
|
532 |
|
533 vis_bin = |
|
534 GST_BIN_CAST (gst_object_get_parent (GST_OBJECT_CAST (play_bin-> |
|
535 visualisation))); |
|
536 |
|
537 if (!GST_IS_BIN (vis_bin) || !GST_IS_PAD (tee_pad)) { |
|
538 goto beach; |
|
539 } |
|
540 |
|
541 vis_src_pad = gst_element_get_pad (play_bin->visualisation, "src"); |
|
542 vis_sink_pad = gst_pad_get_peer (tee_pad); |
|
543 |
|
544 /* Can be fakesink */ |
|
545 if (GST_IS_PAD (vis_src_pad)) { |
|
546 vqueue_pad = gst_pad_get_peer (vis_src_pad); |
|
547 } |
|
548 |
|
549 if (!GST_IS_PAD (vis_sink_pad)) { |
|
550 goto beach; |
|
551 } |
|
552 |
|
553 /* Check the bin's state */ |
|
554 GST_OBJECT_LOCK (vis_bin); |
|
555 bin_state = GST_STATE (vis_bin); |
|
556 GST_OBJECT_UNLOCK (vis_bin); |
|
557 |
|
558 /* Unlink */ |
|
559 gst_pad_unlink (tee_pad, vis_sink_pad); |
|
560 gst_object_unref (vis_sink_pad); |
|
561 vis_sink_pad = NULL; |
|
562 |
|
563 if (GST_IS_PAD (vqueue_pad)) { |
|
564 gst_pad_unlink (vis_src_pad, vqueue_pad); |
|
565 gst_object_unref (vis_src_pad); |
|
566 vis_src_pad = NULL; |
|
567 } |
|
568 |
|
569 /* Remove from vis_bin */ |
|
570 gst_bin_remove (vis_bin, play_bin->visualisation); |
|
571 /* Set state to NULL */ |
|
572 gst_element_set_state (play_bin->visualisation, GST_STATE_NULL); |
|
573 /* And loose our ref */ |
|
574 gst_object_unref (play_bin->visualisation); |
|
575 |
|
576 if (pending_visualisation) { |
|
577 /* Ref this new visualisation element before adding to the bin */ |
|
578 gst_object_ref (pending_visualisation); |
|
579 /* Add the new one */ |
|
580 gst_bin_add (vis_bin, pending_visualisation); |
|
581 /* Synchronizing state */ |
|
582 gst_element_set_state (pending_visualisation, bin_state); |
|
583 |
|
584 vis_sink_pad = gst_element_get_pad (pending_visualisation, "sink"); |
|
585 vis_src_pad = gst_element_get_pad (pending_visualisation, "src"); |
|
586 |
|
587 if (!GST_IS_PAD (vis_sink_pad) || !GST_IS_PAD (vis_src_pad)) { |
|
588 goto beach; |
|
589 } |
|
590 |
|
591 /* Link */ |
|
592 gst_pad_link (tee_pad, vis_sink_pad); |
|
593 gst_pad_link (vis_src_pad, vqueue_pad); |
|
594 } |
|
595 |
|
596 /* We are done */ |
|
597 gst_object_unref (play_bin->visualisation); |
|
598 play_bin->visualisation = pending_visualisation; |
|
599 |
|
600 beach: |
|
601 if (vis_sink_pad) { |
|
602 gst_object_unref (vis_sink_pad); |
|
603 } |
|
604 if (vis_src_pad) { |
|
605 gst_object_unref (vis_src_pad); |
|
606 } |
|
607 if (vqueue_pad) { |
|
608 gst_object_unref (vqueue_pad); |
|
609 } |
|
610 if (vis_bin) { |
|
611 gst_object_unref (vis_bin); |
|
612 } |
|
613 |
|
614 /* Unblock the pad */ |
|
615 gst_pad_set_blocked_async (tee_pad, FALSE, gst_play_bin_vis_unblocked, |
|
616 play_bin); |
|
617 } |
|
618 |
|
619 static void |
|
620 gst_play_bin_set_property (GObject * object, guint prop_id, |
|
621 const GValue * value, GParamSpec * pspec) |
|
622 { |
|
623 GstPlayBin *play_bin; |
|
624 |
|
625 play_bin = GST_PLAY_BIN (object); |
|
626 |
|
627 switch (prop_id) { |
|
628 case ARG_VIDEO_SINK: |
|
629 if (play_bin->video_sink != NULL) { |
|
630 gst_object_unref (play_bin->video_sink); |
|
631 } |
|
632 play_bin->video_sink = g_value_get_object (value); |
|
633 if (play_bin->video_sink != NULL) { |
|
634 gst_object_ref (play_bin->video_sink); |
|
635 gst_object_sink (GST_OBJECT_CAST (play_bin->video_sink)); |
|
636 } |
|
637 /* when changing the videosink, we just remove the |
|
638 * video pipeline from the cache so that it will be |
|
639 * regenerated with the new sink element */ |
|
640 g_hash_table_remove (play_bin->cache, "vbin"); |
|
641 break; |
|
642 case ARG_AUDIO_SINK: |
|
643 if (play_bin->audio_sink != NULL) { |
|
644 gst_object_unref (play_bin->audio_sink); |
|
645 } |
|
646 play_bin->audio_sink = g_value_get_object (value); |
|
647 if (play_bin->audio_sink != NULL) { |
|
648 gst_object_ref (play_bin->audio_sink); |
|
649 gst_object_sink (GST_OBJECT_CAST (play_bin->audio_sink)); |
|
650 } |
|
651 g_hash_table_remove (play_bin->cache, "abin"); |
|
652 break; |
|
653 case ARG_VIS_PLUGIN: |
|
654 { |
|
655 GstElement *pending_visualisation = |
|
656 GST_ELEMENT_CAST (g_value_get_object (value)); |
|
657 |
|
658 /* Take ownership */ |
|
659 if (pending_visualisation) { |
|
660 gst_object_ref (pending_visualisation); |
|
661 gst_object_sink (pending_visualisation); |
|
662 } |
|
663 |
|
664 /* Do we already have a visualisation change pending ? */ |
|
665 GST_OBJECT_LOCK (play_bin); |
|
666 if (play_bin->pending_visualisation) { |
|
667 gst_object_unref (play_bin->pending_visualisation); |
|
668 play_bin->pending_visualisation = pending_visualisation; |
|
669 GST_OBJECT_UNLOCK (play_bin); |
|
670 } else { |
|
671 GST_OBJECT_UNLOCK (play_bin); |
|
672 /* Was there a visualisation already set ? */ |
|
673 if (play_bin->visualisation != NULL) { |
|
674 GstBin *vis_bin = NULL; |
|
675 |
|
676 vis_bin = |
|
677 GST_BIN_CAST (gst_object_get_parent (GST_OBJECT_CAST (play_bin-> |
|
678 visualisation))); |
|
679 |
|
680 /* Check if the visualisation is already in a bin */ |
|
681 if (GST_IS_BIN (vis_bin)) { |
|
682 GstPad *vis_sink_pad = NULL, *tee_pad = NULL; |
|
683 |
|
684 /* Now get tee pad and block it async */ |
|
685 vis_sink_pad = gst_element_get_pad (play_bin->visualisation, |
|
686 "sink"); |
|
687 if (!GST_IS_PAD (vis_sink_pad)) { |
|
688 goto beach; |
|
689 } |
|
690 tee_pad = gst_pad_get_peer (vis_sink_pad); |
|
691 if (!GST_IS_PAD (tee_pad)) { |
|
692 goto beach; |
|
693 } |
|
694 |
|
695 play_bin->pending_visualisation = pending_visualisation; |
|
696 /* Block with callback */ |
|
697 gst_pad_set_blocked_async (tee_pad, TRUE, gst_play_bin_vis_blocked, |
|
698 play_bin); |
|
699 beach: |
|
700 if (vis_sink_pad) { |
|
701 gst_object_unref (vis_sink_pad); |
|
702 } |
|
703 if (tee_pad) { |
|
704 gst_object_unref (tee_pad); |
|
705 } |
|
706 gst_object_unref (vis_bin); |
|
707 } else { |
|
708 play_bin->visualisation = pending_visualisation; |
|
709 } |
|
710 } else { |
|
711 play_bin->visualisation = pending_visualisation; |
|
712 } |
|
713 } |
|
714 break; |
|
715 } |
|
716 case ARG_VOLUME: |
|
717 play_bin->volume = g_value_get_double (value); |
|
718 if (play_bin->volume_element) { |
|
719 g_object_set (G_OBJECT (play_bin->volume_element), "volume", |
|
720 play_bin->volume, NULL); |
|
721 } |
|
722 break; |
|
723 case ARG_FONT_DESC: |
|
724 g_free (play_bin->font_desc); |
|
725 play_bin->font_desc = g_strdup (g_value_get_string (value)); |
|
726 if (play_bin->textoverlay_element) { |
|
727 g_object_set (G_OBJECT (play_bin->textoverlay_element), |
|
728 "font-desc", g_value_get_string (value), NULL); |
|
729 } |
|
730 break; |
|
731 default: |
|
732 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
733 break; |
|
734 } |
|
735 } |
|
736 |
|
737 static void |
|
738 gst_play_bin_get_property (GObject * object, guint prop_id, GValue * value, |
|
739 GParamSpec * pspec) |
|
740 { |
|
741 GstPlayBin *play_bin; |
|
742 |
|
743 play_bin = GST_PLAY_BIN (object); |
|
744 |
|
745 switch (prop_id) { |
|
746 case ARG_VIDEO_SINK: |
|
747 g_value_set_object (value, play_bin->video_sink); |
|
748 break; |
|
749 case ARG_AUDIO_SINK: |
|
750 g_value_set_object (value, play_bin->audio_sink); |
|
751 break; |
|
752 case ARG_VIS_PLUGIN: |
|
753 g_value_set_object (value, play_bin->visualisation); |
|
754 break; |
|
755 case ARG_VOLUME: |
|
756 g_value_set_double (value, play_bin->volume); |
|
757 break; |
|
758 case ARG_FRAME:{ |
|
759 GstBuffer *cur_frame = NULL; |
|
760 |
|
761 gst_buffer_replace (&cur_frame, play_bin->frame); |
|
762 gst_value_take_buffer (value, cur_frame); |
|
763 break; |
|
764 } |
|
765 default: |
|
766 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
767 break; |
|
768 } |
|
769 } |
|
770 |
|
771 /* signal fired when the identity has received a new buffer. This is used for |
|
772 * making screenshots. |
|
773 */ |
|
774 static void |
|
775 handoff (GstElement * identity, GstBuffer * frame, gpointer data) |
|
776 { |
|
777 GstPlayBin *play_bin = GST_PLAY_BIN (data); |
|
778 |
|
779 /* applications need to know the buffer caps, |
|
780 * make sure they are always set on the frame */ |
|
781 if (GST_BUFFER_CAPS (frame) == NULL) { |
|
782 GstPad *pad; |
|
783 |
|
784 if ((pad = gst_element_get_pad (identity, "sink"))) { |
|
785 gst_buffer_set_caps (frame, GST_PAD_CAPS (pad)); |
|
786 gst_object_unref (pad); |
|
787 } |
|
788 } |
|
789 |
|
790 gst_buffer_replace (&play_bin->frame, frame); |
|
791 } |
|
792 |
|
793 static void |
|
794 post_missing_element_message (GstPlayBin * playbin, const gchar * name) |
|
795 { |
|
796 GstMessage *msg; |
|
797 |
|
798 msg = gst_missing_element_message_new (GST_ELEMENT_CAST (playbin), name); |
|
799 gst_element_post_message (GST_ELEMENT_CAST (playbin), msg); |
|
800 } |
|
801 |
|
802 /* make the element (bin) that contains the elements needed to perform |
|
803 * video display. We connect a handoff signal to identity so that we |
|
804 * can grab snapshots. Identity's sinkpad is ghosted to vbin. |
|
805 * |
|
806 * +-------------------------------------------------------------+ |
|
807 * | vbin | |
|
808 * | +--------+ +----------+ +----------+ +---------+ | |
|
809 * | |identity| |colorspace| |videoscale| |videosink| | |
|
810 * | +-sink src-sink src-sink src-sink | | |
|
811 * | | +---+----+ +----------+ +----------+ +---------+ | |
|
812 * sink-+ | | |
|
813 * +----------|--------------------------------------------------+ |
|
814 * handoff |
|
815 */ |
|
816 static GstElement * |
|
817 gen_video_element (GstPlayBin * play_bin) |
|
818 { |
|
819 GstElement *element; |
|
820 GstElement *conv; |
|
821 |
|
822 GstElement *scale; |
|
823 GstElement *sink; |
|
824 GstElement *identity; |
|
825 GstPad *pad; |
|
826 |
|
827 /* first see if we have it in the cache */ |
|
828 element = g_hash_table_lookup (play_bin->cache, "vbin"); |
|
829 if (element != NULL) { |
|
830 return element; |
|
831 } |
|
832 |
|
833 if (play_bin->video_sink) { |
|
834 sink = play_bin->video_sink; |
|
835 } else { |
|
836 sink = gst_element_factory_make ("autovideosink", "videosink"); |
|
837 if (sink == NULL) { |
|
838 sink = gst_element_factory_make ("xvimagesink", "videosink"); |
|
839 } |
|
840 if (sink == NULL) |
|
841 goto no_sinks; |
|
842 } |
|
843 gst_object_ref (sink); |
|
844 g_hash_table_insert (play_bin->cache, "video_sink", sink); |
|
845 |
|
846 /* create a bin to hold objects, as we create them we add them to this bin so |
|
847 * that when something goes wrong we only need to unref the bin */ |
|
848 element = gst_bin_new ("vbin"); |
|
849 gst_bin_add (GST_BIN_CAST (element), sink); |
|
850 |
|
851 conv = gst_element_factory_make ("ffmpegcolorspace", "vconv"); |
|
852 if (conv == NULL) |
|
853 goto no_colorspace; |
|
854 gst_bin_add (GST_BIN_CAST (element), conv); |
|
855 |
|
856 scale = gst_element_factory_make ("videoscale", "vscale"); |
|
857 if (scale == NULL) |
|
858 goto no_videoscale; |
|
859 gst_bin_add (GST_BIN_CAST (element), scale); |
|
860 |
|
861 identity = gst_element_factory_make ("identity", "id"); |
|
862 g_object_set (identity, "silent", TRUE, NULL); |
|
863 g_signal_connect (identity, "handoff", G_CALLBACK (handoff), play_bin); |
|
864 gst_bin_add (GST_BIN_CAST (element), identity); |
|
865 |
|
866 gst_element_link_pads (identity, "src", conv, "sink"); |
|
867 gst_element_link_pads (conv, "src", scale, "sink"); |
|
868 /* be more careful with the pad from the custom sink element, it might not |
|
869 * be named 'sink' */ |
|
870 if (!gst_element_link_pads (scale, "src", sink, NULL)) |
|
871 goto link_failed; |
|
872 |
|
873 pad = gst_element_get_pad (identity, "sink"); |
|
874 gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad)); |
|
875 gst_object_unref (pad); |
|
876 |
|
877 gst_element_set_state (element, GST_STATE_READY); |
|
878 |
|
879 /* since we're gonna add it to a bin but don't want to lose it, |
|
880 * we keep a reference. */ |
|
881 gst_object_ref (element); |
|
882 g_hash_table_insert (play_bin->cache, "vbin", element); |
|
883 |
|
884 return element; |
|
885 |
|
886 /* ERRORS */ |
|
887 no_sinks: |
|
888 { |
|
889 post_missing_element_message (play_bin, "autovideosink"); |
|
890 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN, |
|
891 (_("Both autovideosink and xvimagesink elements are missing.")), |
|
892 (NULL)); |
|
893 return NULL; |
|
894 } |
|
895 no_colorspace: |
|
896 { |
|
897 post_missing_element_message (play_bin, "ffmpegcolorspace"); |
|
898 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN, |
|
899 (_("Missing element '%s' - check your GStreamer installation."), |
|
900 "ffmpegcolorspace"), (NULL)); |
|
901 gst_object_unref (element); |
|
902 return NULL; |
|
903 } |
|
904 |
|
905 no_videoscale: |
|
906 { |
|
907 post_missing_element_message (play_bin, "videoscale"); |
|
908 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN, |
|
909 (_("Missing element '%s' - check your GStreamer installation."), |
|
910 "videoscale"), ("possibly a liboil version mismatch?")); |
|
911 gst_object_unref (element); |
|
912 return NULL; |
|
913 } |
|
914 link_failed: |
|
915 { |
|
916 GST_ELEMENT_ERROR (play_bin, CORE, PAD, |
|
917 (NULL), ("Failed to configure the video sink.")); |
|
918 gst_object_unref (element); |
|
919 return NULL; |
|
920 } |
|
921 } |
|
922 |
|
923 /* make an element for playback of video with subtitles embedded. |
|
924 * |
|
925 * +--------------------------------------------------+ |
|
926 * | tbin +-------------+ | |
|
927 * | +-----+ | textoverlay | +------+ | |
|
928 * | | csp | +--video_sink | | vbin | | |
|
929 * video_sink-sink src+ +-text_sink src-sink | | |
|
930 * | +-----+ | +-------------+ +------+ | |
|
931 * text_sink-------------+ | |
|
932 * +--------------------------------------------------+ |
|
933 * |
|
934 * If there is no subtitle renderer this function will simply return the |
|
935 * videosink without the text_sink pad. |
|
936 */ |
|
937 static GstElement * |
|
938 gen_text_element (GstPlayBin * play_bin) |
|
939 { |
|
940 GstElement *element, *csp, *overlay, *vbin; |
|
941 GstPad *pad; |
|
942 |
|
943 /* Create the video rendering bin, error is posted when this fails. */ |
|
944 vbin = gen_video_element (play_bin); |
|
945 if (!vbin) |
|
946 return NULL; |
|
947 |
|
948 /* Text overlay */ |
|
949 overlay = gst_element_factory_make ("textoverlay", "overlay"); |
|
950 |
|
951 /* If no overlay return the video bin without subtitle support. */ |
|
952 if (!overlay) |
|
953 goto no_overlay; |
|
954 |
|
955 /* Create our bin */ |
|
956 element = gst_bin_new ("textbin"); |
|
957 |
|
958 /* Set some parameters */ |
|
959 g_object_set (G_OBJECT (overlay), |
|
960 "halign", "center", "valign", "bottom", NULL); |
|
961 if (play_bin->font_desc) { |
|
962 g_object_set (G_OBJECT (overlay), "font-desc", play_bin->font_desc, NULL); |
|
963 } |
|
964 |
|
965 /* Take a ref */ |
|
966 play_bin->textoverlay_element = GST_ELEMENT_CAST (gst_object_ref (overlay)); |
|
967 |
|
968 /* we know this will succeed, as the video bin already created one before */ |
|
969 csp = gst_element_factory_make ("ffmpegcolorspace", "subtitlecsp"); |
|
970 |
|
971 /* Add our elements */ |
|
972 gst_bin_add_many (GST_BIN_CAST (element), csp, overlay, vbin, NULL); |
|
973 |
|
974 /* Link */ |
|
975 gst_element_link_pads (csp, "src", overlay, "video_sink"); |
|
976 gst_element_link_pads (overlay, "src", vbin, "sink"); |
|
977 |
|
978 /* Add ghost pads on the subtitle bin */ |
|
979 pad = gst_element_get_pad (overlay, "text_sink"); |
|
980 gst_element_add_pad (element, gst_ghost_pad_new ("text_sink", pad)); |
|
981 gst_object_unref (pad); |
|
982 |
|
983 pad = gst_element_get_pad (csp, "sink"); |
|
984 gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad)); |
|
985 gst_object_unref (pad); |
|
986 |
|
987 /* Set state to READY */ |
|
988 gst_element_set_state (element, GST_STATE_READY); |
|
989 |
|
990 return element; |
|
991 |
|
992 /* ERRORS */ |
|
993 no_overlay: |
|
994 { |
|
995 post_missing_element_message (play_bin, "textoverlay"); |
|
996 GST_WARNING_OBJECT (play_bin, |
|
997 "No overlay (pango) element, subtitles disabled"); |
|
998 return vbin; |
|
999 } |
|
1000 } |
|
1001 |
|
1002 /* make the element (bin) that contains the elements needed to perform |
|
1003 * audio playback. |
|
1004 * |
|
1005 * +-------------------------------------------------------------+ |
|
1006 * | abin | |
|
1007 * | +---------+ +----------+ +---------+ +---------+ | |
|
1008 * | |audioconv| |audioscale| | volume | |audiosink| | |
|
1009 * | +-sink src-sink src-sink src-sink | | |
|
1010 * | | +---------+ +----------+ +---------+ +---------+ | |
|
1011 * sink-+ | |
|
1012 * +-------------------------------------------------------------+ |
|
1013 */ |
|
1014 static GstElement * |
|
1015 gen_audio_element (GstPlayBin * play_bin) |
|
1016 { |
|
1017 gboolean res; |
|
1018 GstElement *element; |
|
1019 GstElement *conv; |
|
1020 GstElement *scale; |
|
1021 GstElement *sink; |
|
1022 GstElement *volume; |
|
1023 GstPad *pad; |
|
1024 |
|
1025 element = g_hash_table_lookup (play_bin->cache, "abin"); |
|
1026 if (element != NULL) |
|
1027 return element; |
|
1028 |
|
1029 if (play_bin->audio_sink) { |
|
1030 sink = play_bin->audio_sink; |
|
1031 } else { |
|
1032 sink = gst_element_factory_make ("autoaudiosink", "audiosink"); |
|
1033 if (sink == NULL) { |
|
1034 #ifdef __SYMBIAN32__ |
|
1035 sink = gst_element_factory_make ("devsoundsink", "audiosink"); |
|
1036 #else |
|
1037 sink = gst_element_factory_make ("alsasink", "audiosink"); |
|
1038 #endif |
|
1039 } |
|
1040 if (sink == NULL) |
|
1041 goto no_sinks; |
|
1042 |
|
1043 play_bin->audio_sink = GST_ELEMENT_CAST (gst_object_ref (sink)); |
|
1044 } |
|
1045 |
|
1046 gst_object_ref (sink); |
|
1047 g_hash_table_insert (play_bin->cache, "audio_sink", sink); |
|
1048 |
|
1049 element = gst_bin_new ("abin"); |
|
1050 gst_bin_add (GST_BIN_CAST (element), sink); |
|
1051 |
|
1052 conv = gst_element_factory_make ("audioconvert", "aconv"); |
|
1053 if (conv == NULL) |
|
1054 goto no_audioconvert; |
|
1055 gst_bin_add (GST_BIN_CAST (element), conv); |
|
1056 |
|
1057 scale = gst_element_factory_make ("audioresample", "aresample"); |
|
1058 if (scale == NULL) |
|
1059 goto no_audioresample; |
|
1060 gst_bin_add (GST_BIN_CAST (element), scale); |
|
1061 #ifndef __SYMBIAN32__ |
|
1062 volume = gst_element_factory_make ("volume", "volume"); |
|
1063 if (volume == NULL) |
|
1064 goto no_volume; |
|
1065 g_object_set (G_OBJECT (volume), "volume", play_bin->volume, NULL); |
|
1066 play_bin->volume_element = volume; |
|
1067 gst_bin_add (GST_BIN_CAST (element), volume); |
|
1068 #endif |
|
1069 |
|
1070 res = gst_element_link_pads (conv, "src", scale, "sink"); |
|
1071 #ifndef __SYMBIAN32__ |
|
1072 res &= gst_element_link_pads (scale, "src", volume, "sink"); |
|
1073 #else |
|
1074 res &= gst_element_link_pads (scale, "src", sink, NULL); |
|
1075 #endif |
|
1076 #ifndef __SYMBIAN32__ |
|
1077 res &= gst_element_link_pads (volume, "src", sink, NULL); |
|
1078 #endif |
|
1079 if (!res) |
|
1080 goto link_failed; |
|
1081 |
|
1082 pad = gst_element_get_pad (conv, "sink"); |
|
1083 gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad)); |
|
1084 gst_object_unref (pad); |
|
1085 |
|
1086 gst_element_set_state (element, GST_STATE_READY); |
|
1087 |
|
1088 /* since we're gonna add it to a bin but don't want to lose it, |
|
1089 * we keep a reference. */ |
|
1090 gst_object_ref (element); |
|
1091 g_hash_table_insert (play_bin->cache, "abin", element); |
|
1092 |
|
1093 return element; |
|
1094 |
|
1095 /* ERRORS */ |
|
1096 no_sinks: |
|
1097 { |
|
1098 post_missing_element_message (play_bin, "autoaudiosink"); |
|
1099 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN, |
|
1100 (_("Both autoaudiosink and alsasink elements are missing.")), (NULL)); |
|
1101 return NULL; |
|
1102 } |
|
1103 no_audioconvert: |
|
1104 { |
|
1105 post_missing_element_message (play_bin, "audioconvert"); |
|
1106 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN, |
|
1107 (_("Missing element '%s' - check your GStreamer installation."), |
|
1108 "audioconvert"), ("possibly a liboil version mismatch?")); |
|
1109 gst_object_unref (element); |
|
1110 return NULL; |
|
1111 } |
|
1112 no_audioresample: |
|
1113 { |
|
1114 post_missing_element_message (play_bin, "audioresample"); |
|
1115 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN, |
|
1116 (_("Missing element '%s' - check your GStreamer installation."), |
|
1117 "audioresample"), ("possibly a liboil version mismatch?")); |
|
1118 gst_object_unref (element); |
|
1119 return NULL; |
|
1120 } |
|
1121 no_volume: |
|
1122 { |
|
1123 post_missing_element_message (play_bin, "volume"); |
|
1124 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN, |
|
1125 (_("Missing element '%s' - check your GStreamer installation."), |
|
1126 "volume"), ("possibly a liboil version mismatch?")); |
|
1127 gst_object_unref (element); |
|
1128 return NULL; |
|
1129 } |
|
1130 link_failed: |
|
1131 { |
|
1132 GST_ELEMENT_ERROR (play_bin, CORE, PAD, |
|
1133 (NULL), ("Failed to configure the audio sink.")); |
|
1134 gst_object_unref (element); |
|
1135 return NULL; |
|
1136 } |
|
1137 } |
|
1138 |
|
1139 /* make the element (bin) that contains the elements needed to perform |
|
1140 * visualisation ouput. The idea is to split the audio using tee, then |
|
1141 * sending the output to the regular audio bin and the other output to |
|
1142 * the vis plugin that transforms it into a video that is rendered with the |
|
1143 * normal video bin. The video and audio bins are run in threads to make sure |
|
1144 * they don't block eachother. |
|
1145 * |
|
1146 * +-----------------------------------------------------------------------+ |
|
1147 * | visbin | |
|
1148 * | +------+ +--------+ +----------------+ | |
|
1149 * | | tee | | aqueue | | abin ... | | |
|
1150 * | +-sink src-sink src-sink | | |
|
1151 * | | | | +--------+ +----------------+ | |
|
1152 * | | | | | |
|
1153 * | | | | +------+ +------------+ +------+ +-----------+ | |
|
1154 * | | | | |vqueue| | audioconv | | vis | | vbin ... | | |
|
1155 * | | | src-sink src-sink + samp src-sink src-sink | | |
|
1156 * | | | | +------+ +------------+ +------+ +-----------+ | |
|
1157 * | | | | | |
|
1158 * | | +------+ | |
|
1159 * sink-+ | |
|
1160 * +-----------------------------------------------------------------------+ |
|
1161 */ |
|
1162 static GstElement * |
|
1163 gen_vis_element (GstPlayBin * play_bin) |
|
1164 { |
|
1165 gboolean res; |
|
1166 GstElement *element; |
|
1167 GstElement *tee; |
|
1168 GstElement *asink; |
|
1169 GstElement *vsink; |
|
1170 GstElement *conv; |
|
1171 GstElement *resamp; |
|
1172 GstElement *conv2; |
|
1173 GstElement *vis; |
|
1174 GstElement *vqueue, *aqueue; |
|
1175 GstPad *pad, *rpad; |
|
1176 |
|
1177 /* errors are already posted when these fail. */ |
|
1178 asink = gen_audio_element (play_bin); |
|
1179 if (!asink) |
|
1180 return NULL; |
|
1181 vsink = gen_video_element (play_bin); |
|
1182 if (!vsink) { |
|
1183 gst_object_unref (asink); |
|
1184 return NULL; |
|
1185 } |
|
1186 |
|
1187 element = gst_bin_new ("visbin"); |
|
1188 tee = gst_element_factory_make ("tee", "tee"); |
|
1189 |
|
1190 vqueue = gst_element_factory_make ("queue", "vqueue"); |
|
1191 aqueue = gst_element_factory_make ("queue", "aqueue"); |
|
1192 |
|
1193 gst_bin_add (GST_BIN_CAST (element), asink); |
|
1194 gst_bin_add (GST_BIN_CAST (element), vqueue); |
|
1195 gst_bin_add (GST_BIN_CAST (element), aqueue); |
|
1196 gst_bin_add (GST_BIN_CAST (element), vsink); |
|
1197 gst_bin_add (GST_BIN_CAST (element), tee); |
|
1198 |
|
1199 conv = gst_element_factory_make ("audioconvert", "aconv"); |
|
1200 if (conv == NULL) |
|
1201 goto no_audioconvert; |
|
1202 gst_bin_add (GST_BIN_CAST (element), conv); |
|
1203 |
|
1204 resamp = gst_element_factory_make ("audioresample", "aresamp"); |
|
1205 if (resamp == NULL) |
|
1206 goto no_audioresample; |
|
1207 gst_bin_add (GST_BIN_CAST (element), resamp); |
|
1208 |
|
1209 conv2 = gst_element_factory_make ("audioconvert", "aconv2"); |
|
1210 if (conv2 == NULL) |
|
1211 goto no_audioconvert; |
|
1212 gst_bin_add (GST_BIN_CAST (element), conv2); |
|
1213 |
|
1214 if (play_bin->visualisation) { |
|
1215 gst_object_ref (play_bin->visualisation); |
|
1216 vis = play_bin->visualisation; |
|
1217 } else { |
|
1218 vis = gst_element_factory_make ("goom", "vis"); |
|
1219 if (!vis) |
|
1220 goto no_goom; |
|
1221 } |
|
1222 gst_bin_add (GST_BIN_CAST (element), vis); |
|
1223 |
|
1224 res = gst_element_link_pads (vqueue, "src", conv, "sink"); |
|
1225 res &= gst_element_link_pads (conv, "src", resamp, "sink"); |
|
1226 res &= gst_element_link_pads (resamp, "src", conv2, "sink"); |
|
1227 res &= gst_element_link_pads (conv2, "src", vis, "sink"); |
|
1228 res &= gst_element_link_pads (vis, "src", vsink, "sink"); |
|
1229 if (!res) |
|
1230 goto link_failed; |
|
1231 |
|
1232 pad = gst_element_get_pad (aqueue, "sink"); |
|
1233 rpad = gst_element_get_request_pad (tee, "src%d"); |
|
1234 gst_pad_link (rpad, pad); |
|
1235 gst_object_unref (rpad); |
|
1236 gst_object_unref (pad); |
|
1237 gst_element_link_pads (aqueue, "src", asink, "sink"); |
|
1238 |
|
1239 pad = gst_element_get_pad (vqueue, "sink"); |
|
1240 rpad = gst_element_get_request_pad (tee, "src%d"); |
|
1241 gst_pad_link (rpad, pad); |
|
1242 gst_object_unref (rpad); |
|
1243 gst_object_unref (pad); |
|
1244 |
|
1245 pad = gst_element_get_pad (tee, "sink"); |
|
1246 gst_element_add_pad (element, gst_ghost_pad_new ("sink", pad)); |
|
1247 gst_object_unref (pad); |
|
1248 |
|
1249 return element; |
|
1250 |
|
1251 /* ERRORS */ |
|
1252 no_audioconvert: |
|
1253 { |
|
1254 post_missing_element_message (play_bin, "audioconvert"); |
|
1255 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN, |
|
1256 (_("Missing element '%s' - check your GStreamer installation."), |
|
1257 "audioconvert"), ("possibly a liboil version mismatch?")); |
|
1258 gst_object_unref (element); |
|
1259 return NULL; |
|
1260 } |
|
1261 no_audioresample: |
|
1262 { |
|
1263 post_missing_element_message (play_bin, "audioresample"); |
|
1264 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN, |
|
1265 (_("Missing element '%s' - check your GStreamer installation."), |
|
1266 "audioresample"), (NULL)); |
|
1267 gst_object_unref (element); |
|
1268 return NULL; |
|
1269 } |
|
1270 no_goom: |
|
1271 { |
|
1272 post_missing_element_message (play_bin, "goom"); |
|
1273 GST_ELEMENT_ERROR (play_bin, CORE, MISSING_PLUGIN, |
|
1274 (_("Missing element '%s' - check your GStreamer installation."), |
|
1275 "goom"), (NULL)); |
|
1276 gst_object_unref (element); |
|
1277 return NULL; |
|
1278 } |
|
1279 link_failed: |
|
1280 { |
|
1281 GST_ELEMENT_ERROR (play_bin, CORE, PAD, |
|
1282 (NULL), ("Failed to configure the visualisation element.")); |
|
1283 gst_object_unref (element); |
|
1284 return NULL; |
|
1285 } |
|
1286 } |
|
1287 |
|
1288 /* get rid of all installed sinks */ |
|
1289 static void |
|
1290 remove_sinks (GstPlayBin * play_bin) |
|
1291 { |
|
1292 GList *sinks; |
|
1293 GstObject *parent; |
|
1294 GstElement *element; |
|
1295 GstPad *pad, *peer; |
|
1296 |
|
1297 if (play_bin->cache == NULL) |
|
1298 return; |
|
1299 |
|
1300 GST_DEBUG ("removesinks"); |
|
1301 element = g_hash_table_lookup (play_bin->cache, "abin"); |
|
1302 if (element != NULL) { |
|
1303 parent = gst_element_get_parent (element); |
|
1304 if (parent != NULL) { |
|
1305 /* we remove the element from the parent so that |
|
1306 * there is no unwanted state change when the parent |
|
1307 * is disposed */ |
|
1308 play_bin->sinks = g_list_remove (play_bin->sinks, element); |
|
1309 gst_element_set_state (element, GST_STATE_NULL); |
|
1310 gst_bin_remove (GST_BIN_CAST (parent), element); |
|
1311 gst_object_unref (parent); |
|
1312 } |
|
1313 pad = gst_element_get_pad (element, "sink"); |
|
1314 if (pad != NULL) { |
|
1315 peer = gst_pad_get_peer (pad); |
|
1316 if (peer != NULL) { |
|
1317 gst_pad_unlink (peer, pad); |
|
1318 gst_object_unref (peer); |
|
1319 } |
|
1320 gst_object_unref (pad); |
|
1321 } |
|
1322 } |
|
1323 element = g_hash_table_lookup (play_bin->cache, "vbin"); |
|
1324 if (element != NULL) { |
|
1325 parent = gst_element_get_parent (element); |
|
1326 if (parent != NULL) { |
|
1327 play_bin->sinks = g_list_remove (play_bin->sinks, element); |
|
1328 gst_element_set_state (element, GST_STATE_NULL); |
|
1329 gst_bin_remove (GST_BIN_CAST (parent), element); |
|
1330 gst_object_unref (parent); |
|
1331 } |
|
1332 pad = gst_element_get_pad (element, "sink"); |
|
1333 if (pad != NULL) { |
|
1334 peer = gst_pad_get_peer (pad); |
|
1335 if (peer != NULL) { |
|
1336 gst_pad_unlink (peer, pad); |
|
1337 gst_object_unref (peer); |
|
1338 } |
|
1339 gst_object_unref (pad); |
|
1340 } |
|
1341 } |
|
1342 |
|
1343 for (sinks = play_bin->sinks; sinks; sinks = g_list_next (sinks)) { |
|
1344 GstElement *element = GST_ELEMENT_CAST (sinks->data); |
|
1345 GstPad *pad; |
|
1346 GstPad *peer; |
|
1347 |
|
1348 pad = gst_element_get_pad (element, "sink"); |
|
1349 |
|
1350 GST_LOG ("removing sink %p", element); |
|
1351 |
|
1352 peer = gst_pad_get_peer (pad); |
|
1353 if (peer) { |
|
1354 gst_pad_unlink (peer, pad); |
|
1355 gst_object_unref (peer); |
|
1356 } |
|
1357 gst_object_unref (pad); |
|
1358 |
|
1359 gst_element_set_state (element, GST_STATE_NULL); |
|
1360 gst_bin_remove (GST_BIN_CAST (play_bin), element); |
|
1361 } |
|
1362 g_list_free (play_bin->sinks); |
|
1363 play_bin->sinks = NULL; |
|
1364 |
|
1365 if (play_bin->visualisation) { |
|
1366 GstElement *vis_bin; |
|
1367 |
|
1368 vis_bin = |
|
1369 GST_ELEMENT_CAST (gst_element_get_parent (play_bin->visualisation)); |
|
1370 |
|
1371 gst_element_set_state (play_bin->visualisation, GST_STATE_NULL); |
|
1372 |
|
1373 if (vis_bin) { |
|
1374 gst_bin_remove (GST_BIN_CAST (vis_bin), play_bin->visualisation); |
|
1375 gst_object_unref (vis_bin); |
|
1376 } |
|
1377 } |
|
1378 |
|
1379 if (play_bin->frame) { |
|
1380 gst_buffer_unref (play_bin->frame); |
|
1381 play_bin->frame = NULL; |
|
1382 } |
|
1383 |
|
1384 if (play_bin->textoverlay_element) { |
|
1385 gst_object_unref (play_bin->textoverlay_element); |
|
1386 play_bin->textoverlay_element = NULL; |
|
1387 } |
|
1388 } |
|
1389 |
|
1390 /* loop over the streams and set up the pipeline to play this |
|
1391 * media file. First we count the number of audio and video streams. |
|
1392 * If there is no video stream but there exists an audio stream, |
|
1393 * we install a visualisation pipeline. |
|
1394 * |
|
1395 * Also make sure to only connect the first audio and video pad. FIXME |
|
1396 * this should eventually be handled with a tuner interface so that |
|
1397 * one can switch the streams. |
|
1398 * |
|
1399 * This function takes ownership of @sink.* |
|
1400 */ |
|
1401 static gboolean |
|
1402 add_sink (GstPlayBin * play_bin, GstElement * sink, GstPad * srcpad, |
|
1403 GstPad * subtitle_pad) |
|
1404 { |
|
1405 GstPad *sinkpad; |
|
1406 GstPadLinkReturn linkres; |
|
1407 GstElement *parent; |
|
1408 GstStateChangeReturn stateret; |
|
1409 GstState state; |
|
1410 |
|
1411 g_return_val_if_fail (sink != NULL, FALSE); |
|
1412 |
|
1413 state = GST_STATE_PAUSED; |
|
1414 |
|
1415 /* this is only for debugging */ |
|
1416 parent = gst_pad_get_parent_element (srcpad); |
|
1417 if (parent) { |
|
1418 GST_DEBUG ("Adding sink %" GST_PTR_FORMAT |
|
1419 " with state %d (parent: %d, peer: %d)", sink, |
|
1420 GST_STATE (sink), GST_STATE (play_bin), GST_STATE (parent)); |
|
1421 gst_object_unref (parent); |
|
1422 } |
|
1423 gst_bin_add (GST_BIN_CAST (play_bin), sink); |
|
1424 |
|
1425 /* bring it to the required state so we can link to the peer without |
|
1426 * breaking the flow */ |
|
1427 stateret = gst_element_set_state (sink, state); |
|
1428 if (stateret == GST_STATE_CHANGE_FAILURE) |
|
1429 goto state_failed; |
|
1430 |
|
1431 /* we found a sink for this stream, now try to install it */ |
|
1432 sinkpad = gst_element_get_pad (sink, "sink"); |
|
1433 linkres = gst_pad_link (srcpad, sinkpad); |
|
1434 gst_object_unref (sinkpad); |
|
1435 |
|
1436 /* try to link the pad of the sink to the stream */ |
|
1437 if (GST_PAD_LINK_FAILED (linkres)) |
|
1438 goto link_failed; |
|
1439 |
|
1440 if (GST_IS_PAD (subtitle_pad)) { |
|
1441 sinkpad = gst_element_get_pad (sink, "text_sink"); |
|
1442 linkres = gst_pad_link (subtitle_pad, sinkpad); |
|
1443 gst_object_unref (sinkpad); |
|
1444 } |
|
1445 |
|
1446 /* try to link the subtitle pad of the sink to the stream, this is not |
|
1447 * fatal. */ |
|
1448 if (GST_PAD_LINK_FAILED (linkres)) |
|
1449 goto subtitle_failed; |
|
1450 |
|
1451 done: |
|
1452 /* we got the sink succesfully linked, now keep the sink |
|
1453 * in our internal list */ |
|
1454 play_bin->sinks = g_list_prepend (play_bin->sinks, sink); |
|
1455 |
|
1456 return TRUE; |
|
1457 |
|
1458 /* ERRORS */ |
|
1459 state_failed: |
|
1460 { |
|
1461 gst_element_set_state (sink, GST_STATE_NULL); |
|
1462 gst_bin_remove (GST_BIN_CAST (play_bin), sink); |
|
1463 GST_DEBUG_OBJECT (play_bin, "state change failure when adding sink"); |
|
1464 return FALSE; |
|
1465 } |
|
1466 link_failed: |
|
1467 { |
|
1468 gchar *capsstr; |
|
1469 GstCaps *caps; |
|
1470 |
|
1471 /* could not link this stream */ |
|
1472 caps = gst_pad_get_caps (srcpad); |
|
1473 capsstr = gst_caps_to_string (caps); |
|
1474 g_warning ("could not link %s: %d", capsstr, linkres); |
|
1475 GST_DEBUG_OBJECT (play_bin, |
|
1476 "link failed when adding sink, caps %s, reason %d", capsstr, linkres); |
|
1477 g_free (capsstr); |
|
1478 gst_caps_unref (caps); |
|
1479 |
|
1480 gst_element_set_state (sink, GST_STATE_NULL); |
|
1481 gst_bin_remove (GST_BIN_CAST (play_bin), sink); |
|
1482 return FALSE; |
|
1483 } |
|
1484 subtitle_failed: |
|
1485 { |
|
1486 GstCaps *caps; |
|
1487 |
|
1488 /* could not link this stream */ |
|
1489 caps = gst_pad_get_caps (subtitle_pad); |
|
1490 GST_WARNING_OBJECT (play_bin, "subtitle link failed when adding sink, " |
|
1491 "caps = %" GST_PTR_FORMAT ", reason %d", caps, linkres); |
|
1492 gst_caps_unref (caps); |
|
1493 |
|
1494 /* not fatal */ |
|
1495 goto done; |
|
1496 } |
|
1497 } |
|
1498 |
|
1499 static void |
|
1500 dummy_blocked_cb (GstPad * pad, gboolean blocked, gpointer user_data) |
|
1501 { |
|
1502 } |
|
1503 |
|
1504 static gboolean |
|
1505 setup_sinks (GstPlayBaseBin * play_base_bin, GstPlayBaseGroup * group) |
|
1506 { |
|
1507 GstPlayBin *play_bin = GST_PLAY_BIN (play_base_bin); |
|
1508 GList *streaminfo = NULL, *s; |
|
1509 gboolean need_vis = FALSE; |
|
1510 gboolean need_text = FALSE; |
|
1511 GstPad *textsrcpad = NULL, *pad = NULL, *origtextsrcpad = NULL; |
|
1512 GstElement *sink; |
|
1513 gboolean res = TRUE; |
|
1514 |
|
1515 /* get rid of existing sinks */ |
|
1516 if (play_bin->sinks) { |
|
1517 remove_sinks (play_bin); |
|
1518 } |
|
1519 GST_DEBUG_OBJECT (play_base_bin, "setupsinks"); |
|
1520 |
|
1521 /* find out what to do */ |
|
1522 if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0 && |
|
1523 group->type[GST_STREAM_TYPE_TEXT - 1].npads > 0) { |
|
1524 need_text = TRUE; |
|
1525 } else if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads == 0 && |
|
1526 group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0 && |
|
1527 play_bin->visualisation != NULL) { |
|
1528 need_vis = TRUE; |
|
1529 } |
|
1530 |
|
1531 /* now actually connect everything */ |
|
1532 g_object_get (G_OBJECT (play_base_bin), "stream-info", &streaminfo, NULL); |
|
1533 for (s = streaminfo; s; s = g_list_next (s)) { |
|
1534 GObject *obj = G_OBJECT (s->data); |
|
1535 gint type; |
|
1536 GstObject *object; |
|
1537 |
|
1538 g_object_get (obj, "type", &type, NULL); |
|
1539 g_object_get (obj, "object", &object, NULL); |
|
1540 } |
|
1541 |
|
1542 /* link audio */ |
|
1543 if (group->type[GST_STREAM_TYPE_AUDIO - 1].npads > 0) { |
|
1544 if (need_vis) { |
|
1545 sink = gen_vis_element (play_bin); |
|
1546 } else { |
|
1547 sink = gen_audio_element (play_bin); |
|
1548 } |
|
1549 if (!sink) |
|
1550 return FALSE; |
|
1551 |
|
1552 pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_AUDIO - 1].preroll, |
|
1553 "src"); |
|
1554 res = add_sink (play_bin, sink, pad, NULL); |
|
1555 gst_object_unref (pad); |
|
1556 } |
|
1557 |
|
1558 /* link video */ |
|
1559 if (group->type[GST_STREAM_TYPE_VIDEO - 1].npads > 0) { |
|
1560 if (need_text) { |
|
1561 GstObject *parent = NULL, *grandparent = NULL; |
|
1562 GstPad *ghost = NULL; |
|
1563 |
|
1564 sink = gen_text_element (play_bin); |
|
1565 textsrcpad = |
|
1566 gst_element_get_pad (group->type[GST_STREAM_TYPE_TEXT - 1].preroll, |
|
1567 "src"); |
|
1568 |
|
1569 /* This pad is from subtitle-bin, we need to create a ghost pad to have |
|
1570 common grandparents */ |
|
1571 parent = gst_object_get_parent (GST_OBJECT_CAST (textsrcpad)); |
|
1572 if (!parent) { |
|
1573 GST_WARNING_OBJECT (textsrcpad, "subtitle pad has no parent !"); |
|
1574 gst_object_unref (textsrcpad); |
|
1575 textsrcpad = NULL; |
|
1576 goto beach; |
|
1577 } |
|
1578 |
|
1579 grandparent = gst_object_get_parent (parent); |
|
1580 if (!grandparent) { |
|
1581 GST_WARNING_OBJECT (textsrcpad, "subtitle pad has no grandparent !"); |
|
1582 gst_object_unref (parent); |
|
1583 gst_object_unref (textsrcpad); |
|
1584 textsrcpad = NULL; |
|
1585 goto beach; |
|
1586 } |
|
1587 |
|
1588 /* We ghost the pad on subtitle_bin only, if the text pad is from the |
|
1589 media demuxer we keep it as it is */ |
|
1590 if (!GST_IS_PLAY_BIN (grandparent)) { |
|
1591 GST_DEBUG_OBJECT (textsrcpad, "this subtitle pad is from a subtitle " |
|
1592 "file, ghosting to a suitable hierarchy"); |
|
1593 /* Block the pad first, because as soon as we add a ghostpad, the queue |
|
1594 * will try and start pushing */ |
|
1595 gst_pad_set_blocked_async (textsrcpad, TRUE, dummy_blocked_cb, NULL); |
|
1596 origtextsrcpad = gst_object_ref (textsrcpad); |
|
1597 |
|
1598 ghost = gst_ghost_pad_new ("text_src", textsrcpad); |
|
1599 if (!GST_IS_PAD (ghost)) { |
|
1600 GST_WARNING_OBJECT (textsrcpad, "failed creating ghost pad for " |
|
1601 "subtitle-bin"); |
|
1602 gst_object_unref (parent); |
|
1603 gst_object_unref (grandparent); |
|
1604 gst_object_unref (textsrcpad); |
|
1605 textsrcpad = NULL; |
|
1606 goto beach; |
|
1607 } |
|
1608 |
|
1609 gst_pad_set_active (ghost, TRUE); |
|
1610 if (gst_element_add_pad (GST_ELEMENT_CAST (grandparent), ghost)) { |
|
1611 gst_object_unref (textsrcpad); |
|
1612 textsrcpad = gst_object_ref (ghost); |
|
1613 } else { |
|
1614 GST_WARNING_OBJECT (ghost, "failed adding ghost pad on subtitle-bin"); |
|
1615 gst_pad_set_active (ghost, FALSE); |
|
1616 gst_object_unref (ghost); |
|
1617 gst_object_unref (textsrcpad); |
|
1618 textsrcpad = NULL; |
|
1619 } |
|
1620 } else { |
|
1621 GST_DEBUG_OBJECT (textsrcpad, "this subtitle pad is from the demuxer " |
|
1622 "no changes to hierarchy needed"); |
|
1623 } |
|
1624 |
|
1625 gst_object_unref (parent); |
|
1626 gst_object_unref (grandparent); |
|
1627 } else { |
|
1628 sink = gen_video_element (play_bin); |
|
1629 } |
|
1630 beach: |
|
1631 if (!sink) |
|
1632 return FALSE; |
|
1633 pad = gst_element_get_pad (group->type[GST_STREAM_TYPE_VIDEO - 1].preroll, |
|
1634 "src"); |
|
1635 res = add_sink (play_bin, sink, pad, textsrcpad); |
|
1636 gst_object_unref (pad); |
|
1637 if (textsrcpad) |
|
1638 gst_object_unref (textsrcpad); |
|
1639 if (origtextsrcpad) { |
|
1640 gst_pad_set_blocked_async (origtextsrcpad, FALSE, dummy_blocked_cb, NULL); |
|
1641 gst_object_unref (origtextsrcpad); |
|
1642 } |
|
1643 } |
|
1644 |
|
1645 /* remove the sinks now, pipeline get_state will now wait for the |
|
1646 * sinks to preroll */ |
|
1647 if (play_bin->fakesink) { |
|
1648 gst_element_set_state (play_bin->fakesink, GST_STATE_NULL); |
|
1649 gst_bin_remove (GST_BIN_CAST (play_bin), play_bin->fakesink); |
|
1650 play_bin->fakesink = NULL; |
|
1651 } |
|
1652 |
|
1653 return res; |
|
1654 } |
|
1655 |
|
1656 static void |
|
1657 playbin_set_subtitles_visible (GstPlayBaseBin * play_base_bin, gboolean visible) |
|
1658 { |
|
1659 GstPlayBin *playbin = GST_PLAY_BIN (play_base_bin); |
|
1660 |
|
1661 /* we're ignoring the case of someone setting the 'current-text' property |
|
1662 * before textoverlay is set up (which is probably okay, since playbasebin |
|
1663 * will just select the first subtitle stream as active stream regardless) */ |
|
1664 if (playbin->textoverlay_element != NULL) { |
|
1665 GST_LOG_OBJECT (playbin, "setting subtitle visibility to %d", visible); |
|
1666 g_object_set (playbin->textoverlay_element, "silent", !visible, NULL); |
|
1667 } |
|
1668 } |
|
1669 |
|
1670 /* Send an event to our sinks until one of them works; don't then send to the |
|
1671 * remaining sinks (unlike GstBin) |
|
1672 */ |
|
1673 static gboolean |
|
1674 gst_play_bin_send_event_to_sink (GstPlayBin * play_bin, GstEvent * event) |
|
1675 { |
|
1676 GList *sinks = play_bin->sinks; |
|
1677 gboolean res = TRUE; |
|
1678 |
|
1679 while (sinks) { |
|
1680 GstElement *sink = GST_ELEMENT_CAST (sinks->data); |
|
1681 |
|
1682 gst_event_ref (event); |
|
1683 if ((res = gst_element_send_event (sink, event))) { |
|
1684 GST_DEBUG_OBJECT (play_bin, |
|
1685 "Sent event succesfully to sink %" GST_PTR_FORMAT, sink); |
|
1686 break; |
|
1687 } |
|
1688 GST_DEBUG_OBJECT (play_bin, |
|
1689 "Event failed when sent to sink %" GST_PTR_FORMAT, sink); |
|
1690 |
|
1691 sinks = g_list_next (sinks); |
|
1692 } |
|
1693 |
|
1694 gst_event_unref (event); |
|
1695 |
|
1696 return res; |
|
1697 } |
|
1698 |
|
1699 /* We only want to send the event to a single sink (overriding GstBin's |
|
1700 * behaviour), but we want to keep GstPipeline's behaviour - wrapping seek |
|
1701 * events appropriately. So, this is a messy duplication of code. */ |
|
1702 static gboolean |
|
1703 gst_play_bin_send_event (GstElement * element, GstEvent * event) |
|
1704 { |
|
1705 gboolean res = FALSE; |
|
1706 GstEventType event_type = GST_EVENT_TYPE (event); |
|
1707 |
|
1708 switch (event_type) { |
|
1709 case GST_EVENT_SEEK: |
|
1710 GST_DEBUG_OBJECT (element, "Sending seek event to a sink"); |
|
1711 res = gst_play_bin_send_event_to_sink (GST_PLAY_BIN (element), event); |
|
1712 break; |
|
1713 default: |
|
1714 res = parent_class->send_event (element, event); |
|
1715 break; |
|
1716 } |
|
1717 |
|
1718 return res; |
|
1719 } |
|
1720 |
|
1721 static void |
|
1722 value_list_append_structure_list (GValue * list_val, GstStructure ** first, |
|
1723 GList * structure_list) |
|
1724 { |
|
1725 GList *l; |
|
1726 |
|
1727 for (l = structure_list; l != NULL; l = l->next) { |
|
1728 GValue val = { 0, }; |
|
1729 |
|
1730 if (*first == NULL) |
|
1731 *first = gst_structure_copy ((GstStructure *) l->data); |
|
1732 |
|
1733 g_value_init (&val, GST_TYPE_STRUCTURE); |
|
1734 g_value_take_boxed (&val, gst_structure_copy ((GstStructure *) l->data)); |
|
1735 gst_value_list_append_value (list_val, &val); |
|
1736 g_value_unset (&val); |
|
1737 } |
|
1738 } |
|
1739 |
|
1740 /* if it's a redirect message with multiple redirect locations we might |
|
1741 * want to pick a different 'best' location depending on the required |
|
1742 * bitrates and the connection speed */ |
|
1743 static GstMessage * |
|
1744 gst_play_bin_handle_redirect_message (GstPlayBin * playbin, GstMessage * msg) |
|
1745 { |
|
1746 const GValue *locations_list, *location_val; |
|
1747 GstMessage *new_msg; |
|
1748 GstStructure *new_structure = NULL; |
|
1749 GList *l_good = NULL, *l_neutral = NULL, *l_bad = NULL; |
|
1750 GValue new_list = { 0, }; |
|
1751 guint size, i; |
|
1752 GstPlayBaseBin *playbasebin = GST_PLAY_BASE_BIN (playbin); |
|
1753 guint connection_speed = playbasebin->connection_speed; |
|
1754 |
|
1755 GST_DEBUG_OBJECT (playbin, "redirect message: %" GST_PTR_FORMAT, msg); |
|
1756 GST_DEBUG_OBJECT (playbin, "connection speed: %u", connection_speed); |
|
1757 |
|
1758 if (connection_speed == 0 || msg->structure == NULL) |
|
1759 return msg; |
|
1760 |
|
1761 locations_list = gst_structure_get_value (msg->structure, "locations"); |
|
1762 if (locations_list == NULL) |
|
1763 return msg; |
|
1764 |
|
1765 size = gst_value_list_get_size (locations_list); |
|
1766 if (size < 2) |
|
1767 return msg; |
|
1768 |
|
1769 /* maintain existing order as much as possible, just sort references |
|
1770 * with too high a bitrate to the end (the assumption being that if |
|
1771 * bitrates are given they are given for all interesting streams and |
|
1772 * that the you-need-at-least-version-xyz redirect has the same bitrate |
|
1773 * as the lowest referenced redirect alternative) */ |
|
1774 for (i = 0; i < size; ++i) { |
|
1775 const GstStructure *s; |
|
1776 gint bitrate = 0; |
|
1777 |
|
1778 location_val = gst_value_list_get_value (locations_list, i); |
|
1779 s = (const GstStructure *) g_value_get_boxed (location_val); |
|
1780 if (!gst_structure_get_int (s, "minimum-bitrate", &bitrate) || bitrate <= 0) { |
|
1781 GST_DEBUG_OBJECT (playbin, "no bitrate: %" GST_PTR_FORMAT, s); |
|
1782 l_neutral = g_list_append (l_neutral, (gpointer) s); |
|
1783 } else if (bitrate > connection_speed) { |
|
1784 GST_DEBUG_OBJECT (playbin, "bitrate too high: %" GST_PTR_FORMAT, s); |
|
1785 l_bad = g_list_append (l_bad, (gpointer) s); |
|
1786 } else if (bitrate <= connection_speed) { |
|
1787 GST_DEBUG_OBJECT (playbin, "bitrate OK: %" GST_PTR_FORMAT, s); |
|
1788 l_good = g_list_append (l_good, (gpointer) s); |
|
1789 } |
|
1790 } |
|
1791 |
|
1792 g_value_init (&new_list, GST_TYPE_LIST); |
|
1793 value_list_append_structure_list (&new_list, &new_structure, l_good); |
|
1794 value_list_append_structure_list (&new_list, &new_structure, l_neutral); |
|
1795 value_list_append_structure_list (&new_list, &new_structure, l_bad); |
|
1796 gst_structure_set_value (new_structure, "locations", &new_list); |
|
1797 g_value_unset (&new_list); |
|
1798 |
|
1799 g_list_free (l_good); |
|
1800 g_list_free (l_neutral); |
|
1801 g_list_free (l_bad); |
|
1802 |
|
1803 new_msg = gst_message_new_element (msg->src, new_structure); |
|
1804 gst_message_unref (msg); |
|
1805 |
|
1806 GST_DEBUG_OBJECT (playbin, "new redirect message: %" GST_PTR_FORMAT, new_msg); |
|
1807 return new_msg; |
|
1808 } |
|
1809 |
|
1810 static void |
|
1811 gst_play_bin_handle_message (GstBin * bin, GstMessage * msg) |
|
1812 { |
|
1813 if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ELEMENT && msg->structure != NULL |
|
1814 && gst_structure_has_name (msg->structure, "redirect")) { |
|
1815 msg = gst_play_bin_handle_redirect_message (GST_PLAY_BIN (bin), msg); |
|
1816 } |
|
1817 |
|
1818 GST_BIN_CLASS (parent_class)->handle_message (bin, msg); |
|
1819 } |
|
1820 |
|
1821 static GstStateChangeReturn |
|
1822 gst_play_bin_change_state (GstElement * element, GstStateChange transition) |
|
1823 { |
|
1824 GstStateChangeReturn ret; |
|
1825 GstPlayBin *play_bin; |
|
1826 |
|
1827 play_bin = GST_PLAY_BIN (element); |
|
1828 |
|
1829 |
|
1830 switch (transition) { |
|
1831 case GST_STATE_CHANGE_READY_TO_PAUSED: |
|
1832 /* this really is the easiest way to make the state change return |
|
1833 * ASYNC until we added the sinks */ |
|
1834 if (!play_bin->fakesink) { |
|
1835 play_bin->fakesink = gst_element_factory_make ("fakesink", "test"); |
|
1836 gst_bin_add (GST_BIN_CAST (play_bin), play_bin->fakesink); |
|
1837 } |
|
1838 break; |
|
1839 default: |
|
1840 break; |
|
1841 } |
|
1842 |
|
1843 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
|
1844 if (ret == GST_STATE_CHANGE_FAILURE) |
|
1845 return ret; |
|
1846 |
|
1847 switch (transition) { |
|
1848 case GST_STATE_CHANGE_READY_TO_PAUSED: |
|
1849 /* remember us being a live pipeline */ |
|
1850 play_bin->is_live = (ret == GST_STATE_CHANGE_NO_PREROLL); |
|
1851 GST_DEBUG_OBJECT (play_bin, "is live: %d", play_bin->is_live); |
|
1852 break; |
|
1853 case GST_STATE_CHANGE_PLAYING_TO_PAUSED: |
|
1854 /* FIXME Release audio device when we implement that */ |
|
1855 break; |
|
1856 case GST_STATE_CHANGE_PAUSED_TO_READY: |
|
1857 case GST_STATE_CHANGE_READY_TO_NULL: |
|
1858 /* remove sinks we added */ |
|
1859 remove_sinks (play_bin); |
|
1860 /* and there might be a fakesink we need to clean up now */ |
|
1861 if (play_bin->fakesink) { |
|
1862 gst_element_set_state (play_bin->fakesink, GST_STATE_NULL); |
|
1863 gst_bin_remove (GST_BIN_CAST (play_bin), play_bin->fakesink); |
|
1864 play_bin->fakesink = NULL; |
|
1865 } |
|
1866 break; |
|
1867 default: |
|
1868 break; |
|
1869 } |
|
1870 |
|
1871 return ret; |
|
1872 } |
|
1873 #ifdef __SYMBIAN32__ |
|
1874 EXPORT_C |
|
1875 #endif |
|
1876 |
|
1877 |
|
1878 gboolean |
|
1879 gst_play_bin_plugin_init (GstPlugin * plugin) |
|
1880 { |
|
1881 GST_DEBUG_CATEGORY_INIT (gst_play_bin_debug, "playbin", 0, "play bin"); |
|
1882 |
|
1883 return gst_element_register (plugin, "playbin", GST_RANK_NONE, |
|
1884 GST_TYPE_PLAY_BIN); |
|
1885 } |
|
1886 |
|
1887 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, |
|
1888 GST_VERSION_MINOR, |
|
1889 "playbin", |
|
1890 "plays a audio file", |
|
1891 gst_play_bin_plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); |
|
1892 |
|
1893 #ifdef __SYMBIAN32__ |
|
1894 EXPORT_C |
|
1895 #endif |
|
1896 GstPluginDesc* _GST_PLUGIN_DESC() |
|
1897 { |
|
1898 return &gst_plugin_desc; |
|
1899 } |
|
1900 |