-base: port elements to new video caps
[platform/upstream/gstreamer.git] / gst / playback / gstplaysinkaudioconvert.c
1 /* GStreamer
2  * Copyright (C) <2011> Sebastian Dröge <sebastian.droege@collabora.co.uk>
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 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "gstplaysinkaudioconvert.h"
25
26 #include <gst/pbutils/pbutils.h>
27 #include <gst/gst-i18n-plugin.h>
28
29 GST_DEBUG_CATEGORY_STATIC (gst_play_sink_audio_convert_debug);
30 #define GST_CAT_DEFAULT gst_play_sink_audio_convert_debug
31
32 #define parent_class gst_play_sink_audio_convert_parent_class
33
34 G_DEFINE_TYPE (GstPlaySinkAudioConvert, gst_play_sink_audio_convert,
35     GST_TYPE_BIN);
36
37 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
38     GST_PAD_SRC,
39     GST_PAD_ALWAYS,
40     GST_STATIC_CAPS_ANY);
41
42 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
43     GST_PAD_SINK,
44     GST_PAD_ALWAYS,
45     GST_STATIC_CAPS_ANY);
46
47 static gboolean
48 is_raw_caps (GstCaps * caps)
49 {
50   gint i, n;
51   GstStructure *s;
52   const gchar *name;
53
54   n = gst_caps_get_size (caps);
55   for (i = 0; i < n; i++) {
56     s = gst_caps_get_structure (caps, i);
57     name = gst_structure_get_name (s);
58     if (!g_str_has_prefix (name, "audio/x-raw"))
59       return FALSE;
60   }
61
62   return TRUE;
63 }
64
65 static void
66 post_missing_element_message (GstPlaySinkAudioConvert * self,
67     const gchar * name)
68 {
69   GstMessage *msg;
70
71   msg = gst_missing_element_message_new (GST_ELEMENT_CAST (self), name);
72   gst_element_post_message (GST_ELEMENT_CAST (self), msg);
73 }
74
75 static GstProbeReturn
76 pad_blocked_cb (GstPad * pad, GstProbeType type, gpointer type_data,
77     gpointer user_data)
78 {
79   GstPlaySinkAudioConvert *self = user_data;
80   GstPad *peer;
81   GstCaps *caps;
82   gboolean raw;
83
84   GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self);
85   GST_DEBUG_OBJECT (self, "Pad blocked");
86
87   /* There must be a peer at this point */
88   peer = gst_pad_get_peer (self->sinkpad);
89   caps = gst_pad_get_negotiated_caps (peer);
90   if (!caps)
91     caps = gst_pad_get_caps (peer, NULL);
92   gst_object_unref (peer);
93
94   raw = is_raw_caps (caps);
95   GST_DEBUG_OBJECT (self, "Caps %" GST_PTR_FORMAT " are raw: %d", caps, raw);
96   gst_caps_unref (caps);
97
98   if (raw == self->raw)
99     goto unblock;
100   self->raw = raw;
101
102   if (raw) {
103     GstBin *bin = GST_BIN_CAST (self);
104     GstElement *head = NULL, *prev = NULL;
105     GstPad *pad;
106
107     GST_DEBUG_OBJECT (self, "Creating raw conversion pipeline");
108
109     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), NULL);
110     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL);
111
112     if (self->use_converters) {
113       self->conv = gst_element_factory_make ("audioconvert", "conv");
114       if (self->conv == NULL) {
115         post_missing_element_message (self, "audioconvert");
116         GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN,
117             (_("Missing element '%s' - check your GStreamer installation."),
118                 "audioconvert"), ("audio rendering might fail"));
119       } else {
120         gst_bin_add (bin, self->conv);
121         gst_element_sync_state_with_parent (self->conv);
122         prev = head = self->conv;
123       }
124
125       self->resample = gst_element_factory_make ("audioresample", "resample");
126       if (self->resample == NULL) {
127         post_missing_element_message (self, "audioresample");
128         GST_ELEMENT_WARNING (self, CORE, MISSING_PLUGIN,
129             (_("Missing element '%s' - check your GStreamer installation."),
130                 "audioresample"), ("possibly a liboil version mismatch?"));
131       } else {
132         gst_bin_add (bin, self->resample);
133         gst_element_sync_state_with_parent (self->resample);
134         if (prev) {
135           if (!gst_element_link_pads_full (prev, "src", self->resample, "sink",
136                   GST_PAD_LINK_CHECK_TEMPLATE_CAPS))
137             goto link_failed;
138         } else {
139           head = self->resample;
140         }
141         prev = self->resample;
142       }
143     }
144
145     if (self->use_volume && self->volume) {
146       gst_bin_add (bin, gst_object_ref (self->volume));
147       gst_element_sync_state_with_parent (self->volume);
148       if (prev) {
149         if (!gst_element_link_pads_full (prev, "src", self->volume, "sink",
150                 GST_PAD_LINK_CHECK_TEMPLATE_CAPS))
151           goto link_failed;
152       } else {
153         head = self->volume;
154       }
155       prev = self->volume;
156     }
157
158     if (head) {
159       pad = gst_element_get_static_pad (head, "sink");
160       gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), pad);
161       gst_object_unref (pad);
162     }
163
164     if (prev) {
165       pad = gst_element_get_static_pad (prev, "src");
166       gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), pad);
167       gst_object_unref (pad);
168     }
169
170     if (!head && !prev) {
171       gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad),
172           self->sink_proxypad);
173     }
174
175     GST_DEBUG_OBJECT (self, "Raw conversion pipeline created");
176   } else {
177     GstBin *bin = GST_BIN_CAST (self);
178
179     GST_DEBUG_OBJECT (self, "Removing raw conversion pipeline");
180
181     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), NULL);
182     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL);
183
184     if (self->conv) {
185       gst_element_set_state (self->conv, GST_STATE_NULL);
186       gst_bin_remove (bin, self->conv);
187       self->conv = NULL;
188     }
189     if (self->resample) {
190       gst_element_set_state (self->resample, GST_STATE_NULL);
191       gst_bin_remove (bin, self->resample);
192       self->resample = NULL;
193     }
194     if (self->volume) {
195       gst_element_set_state (self->volume, GST_STATE_NULL);
196       if (GST_OBJECT_PARENT (self->volume) == GST_OBJECT_CAST (self)) {
197         gst_bin_remove (GST_BIN_CAST (self), self->volume);
198       }
199     }
200
201     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad),
202         self->sink_proxypad);
203
204     GST_DEBUG_OBJECT (self, "Raw conversion pipeline removed");
205   }
206
207 unblock:
208   self->sink_proxypad_block_id = 0;
209   GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self);
210
211   return GST_PROBE_REMOVE;
212
213 link_failed:
214   {
215     GST_ELEMENT_ERROR (self, CORE, PAD,
216         (NULL), ("Failed to configure the audio converter."));
217     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad),
218         self->sink_proxypad);
219     self->sink_proxypad_block_id = 0;
220     GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self);
221
222     return GST_PROBE_REMOVE;
223   }
224 }
225
226 static void
227 block_proxypad (GstPlaySinkAudioConvert * self)
228 {
229   if (self->sink_proxypad_block_id == 0) {
230     self->sink_proxypad_block_id =
231         gst_pad_add_probe (self->sink_proxypad, GST_PROBE_TYPE_BLOCK,
232         pad_blocked_cb, gst_object_ref (self),
233         (GDestroyNotify) gst_object_unref);
234   }
235 }
236
237 static void
238 unblock_proxypad (GstPlaySinkAudioConvert * self)
239 {
240   if (self->sink_proxypad_block_id != 0) {
241     gst_pad_remove_probe (self->sink_proxypad, self->sink_proxypad_block_id);
242     self->sink_proxypad_block_id = 0;
243   }
244 }
245
246 static gboolean
247 gst_play_sink_audio_convert_sink_setcaps (GstPlaySinkAudioConvert * self,
248     GstCaps * caps)
249 {
250   GstStructure *s;
251   const gchar *name;
252   gboolean reconfigure = FALSE;
253
254   GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self);
255   s = gst_caps_get_structure (caps, 0);
256   name = gst_structure_get_name (s);
257
258   if (g_str_has_prefix (name, "audio/x-raw")) {
259     if (!self->raw && !gst_pad_is_blocked (self->sink_proxypad)) {
260       GST_DEBUG_OBJECT (self, "Changing caps from non-raw to raw");
261       reconfigure = TRUE;
262       block_proxypad (self);
263     }
264   } else {
265     if (self->raw && !gst_pad_is_blocked (self->sink_proxypad)) {
266       GST_DEBUG_OBJECT (self, "Changing caps from raw to non-raw");
267       reconfigure = TRUE;
268       block_proxypad (self);
269     }
270   }
271
272   /* Otherwise the setcaps below fails */
273   if (reconfigure) {
274     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->sinkpad), NULL);
275     gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad), NULL);
276   }
277
278   GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self);
279
280   GST_DEBUG_OBJECT (self, "Setting sink caps %" GST_PTR_FORMAT, caps);
281
282   return TRUE;
283 }
284
285 static gboolean
286 gst_play_sink_audio_convert_sink_event (GstPad * pad, GstEvent * event)
287 {
288   GstPlaySinkAudioConvert *self =
289       GST_PLAY_SINK_AUDIO_CONVERT (gst_pad_get_parent (pad));
290   gboolean ret;
291
292   switch (GST_EVENT_TYPE (event)) {
293     case GST_EVENT_CAPS:
294     {
295       GstCaps *caps;
296
297       gst_event_parse_caps (event, &caps);
298       ret = gst_play_sink_audio_convert_sink_setcaps (self, caps);
299       break;
300     }
301     default:
302       break;
303   }
304
305   ret = gst_proxy_pad_event_default (pad, gst_event_ref (event));
306
307   switch (GST_EVENT_TYPE (event)) {
308     case GST_EVENT_SEGMENT:
309       GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self);
310       GST_DEBUG_OBJECT (self, "Segment before %" GST_SEGMENT_FORMAT,
311           &self->segment);
312       gst_event_copy_segment (event, &self->segment);
313       GST_DEBUG_OBJECT (self, "Segment after %" GST_SEGMENT_FORMAT,
314           &self->segment);
315       GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self);
316       break;
317     case GST_EVENT_FLUSH_STOP:
318       GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self);
319       GST_DEBUG_OBJECT (self, "Resetting segment");
320       gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
321       GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self);
322       break;
323     default:
324       break;
325   }
326
327   gst_event_unref (event);
328   gst_object_unref (self);
329
330   return ret;
331 }
332
333 static GstCaps *
334 gst_play_sink_audio_convert_getcaps (GstPad * pad, GstCaps * filter)
335 {
336   GstPlaySinkAudioConvert *self =
337       GST_PLAY_SINK_AUDIO_CONVERT (gst_pad_get_parent (pad));
338   GstCaps *ret;
339   GstPad *otherpad, *peer;
340
341   GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self);
342   if (pad == self->srcpad)
343     otherpad = gst_object_ref (self->sinkpad);
344   else
345     otherpad = gst_object_ref (self->srcpad);
346   GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self);
347
348   peer = gst_pad_get_peer (otherpad);
349   if (peer) {
350     ret = gst_pad_get_caps (peer, filter);
351     gst_object_unref (peer);
352   } else {
353     ret = (filter ? gst_caps_ref (filter) : gst_caps_new_any ());
354   }
355
356   gst_object_unref (otherpad);
357   gst_object_unref (self);
358
359   return ret;
360 }
361
362 static void
363 gst_play_sink_audio_convert_finalize (GObject * object)
364 {
365   GstPlaySinkAudioConvert *self = GST_PLAY_SINK_AUDIO_CONVERT_CAST (object);
366
367   if (self->volume)
368     gst_object_unref (self->volume);
369
370   gst_object_unref (self->sink_proxypad);
371   g_mutex_free (self->lock);
372
373   G_OBJECT_CLASS (parent_class)->finalize (object);
374 }
375
376 static GstStateChangeReturn
377 gst_play_sink_audio_convert_change_state (GstElement * element,
378     GstStateChange transition)
379 {
380   GstStateChangeReturn ret;
381   GstPlaySinkAudioConvert *self = GST_PLAY_SINK_AUDIO_CONVERT_CAST (element);
382
383   switch (transition) {
384     case GST_STATE_CHANGE_PAUSED_TO_READY:
385       GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self);
386       unblock_proxypad (self);
387       GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self);
388       break;
389     default:
390       break;
391   }
392
393   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
394   if (ret == GST_STATE_CHANGE_FAILURE)
395     return ret;
396
397   switch (transition) {
398     case GST_STATE_CHANGE_PAUSED_TO_READY:
399       GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self);
400       gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
401       if (self->conv) {
402         gst_element_set_state (self->conv, GST_STATE_NULL);
403         gst_bin_remove (GST_BIN_CAST (self), self->conv);
404         self->conv = NULL;
405       }
406       if (self->resample) {
407         gst_element_set_state (self->resample, GST_STATE_NULL);
408         gst_bin_remove (GST_BIN_CAST (self), self->resample);
409         self->resample = NULL;
410       }
411       if (self->volume) {
412         gst_element_set_state (self->volume, GST_STATE_NULL);
413         if (GST_OBJECT_PARENT (self->volume) == GST_OBJECT_CAST (self)) {
414           gst_bin_remove (GST_BIN_CAST (self), self->volume);
415         }
416       }
417       gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad),
418           self->sink_proxypad);
419       self->raw = FALSE;
420       GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self);
421       break;
422     case GST_STATE_CHANGE_READY_TO_PAUSED:
423       GST_PLAY_SINK_AUDIO_CONVERT_LOCK (self);
424       unblock_proxypad (self);
425       GST_PLAY_SINK_AUDIO_CONVERT_UNLOCK (self);
426     default:
427       break;
428   }
429
430   return ret;
431 }
432
433 static void
434 gst_play_sink_audio_convert_class_init (GstPlaySinkAudioConvertClass * klass)
435 {
436   GObjectClass *gobject_class;
437   GstElementClass *gstelement_class;
438
439   GST_DEBUG_CATEGORY_INIT (gst_play_sink_audio_convert_debug,
440       "playsinkaudioconvert", 0, "play bin");
441
442   gobject_class = (GObjectClass *) klass;
443   gstelement_class = (GstElementClass *) klass;
444
445   gobject_class->finalize = gst_play_sink_audio_convert_finalize;
446
447   gst_element_class_add_pad_template (gstelement_class,
448       gst_static_pad_template_get (&srctemplate));
449   gst_element_class_add_pad_template (gstelement_class,
450       gst_static_pad_template_get (&sinktemplate));
451   gst_element_class_set_details_simple (gstelement_class,
452       "Player Sink Audio Converter", "Audio/Bin/Converter",
453       "Convenience bin for audio conversion",
454       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
455
456   gstelement_class->change_state =
457       GST_DEBUG_FUNCPTR (gst_play_sink_audio_convert_change_state);
458 }
459
460 static void
461 gst_play_sink_audio_convert_init (GstPlaySinkAudioConvert * self)
462 {
463   GstPadTemplate *templ;
464
465   self->lock = g_mutex_new ();
466   gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED);
467
468   templ = gst_static_pad_template_get (&sinktemplate);
469   self->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink", templ);
470   gst_pad_set_event_function (self->sinkpad,
471       GST_DEBUG_FUNCPTR (gst_play_sink_audio_convert_sink_event));
472   gst_pad_set_getcaps_function (self->sinkpad,
473       GST_DEBUG_FUNCPTR (gst_play_sink_audio_convert_getcaps));
474
475   self->sink_proxypad =
476       GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD (self->sinkpad)));
477
478   gst_element_add_pad (GST_ELEMENT_CAST (self), self->sinkpad);
479   gst_object_unref (templ);
480
481   templ = gst_static_pad_template_get (&srctemplate);
482   self->srcpad = gst_ghost_pad_new_no_target_from_template ("src", templ);
483   gst_pad_set_getcaps_function (self->srcpad,
484       GST_DEBUG_FUNCPTR (gst_play_sink_audio_convert_getcaps));
485   gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad);
486   gst_object_unref (templ);
487
488   gst_ghost_pad_set_target (GST_GHOST_PAD_CAST (self->srcpad),
489       self->sink_proxypad);
490
491   /* FIXME: Only create this on demand but for now we need
492    * it to always exist because of playsink's volume proxying
493    * logic.
494    */
495   self->volume = gst_element_factory_make ("volume", "volume");
496   if (self->volume)
497     gst_object_ref_sink (self->volume);
498 }