tizen 2.3.1 release
[framework/multimedia/gst-plugins-base0.10.git] / gst / playback / gstplaysink.c
old mode 100644 (file)
new mode 100755 (executable)
index 326baa6..1f0f9c1
@@ -1,5 +1,6 @@
 /* GStreamer
  * Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
+ * Copyright (C) <2011> Sebastian Dröge <sebastian.droege@collabora.co.uk>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
 #include "config.h"
 #endif
 
+/* FIXME 0.11: suppress warnings for deprecated API such as GStaticRecMutex
+ * with newer GLib versions (>= 2.31.0) */
+#define GLIB_DISABLE_DEPRECATION_WARNINGS
+
 #include <string.h>
 #include <gst/gst.h>
 
@@ -30,6 +35,8 @@
 
 #include "gstplaysink.h"
 #include "gststreamsynchronizer.h"
+#include "gstplaysinkvideoconvert.h"
+#include "gstplaysinkaudioconvert.h"
 
 GST_DEBUG_CATEGORY_STATIC (gst_play_sink_debug);
 #define GST_CAT_DEFAULT gst_play_sink_debug
@@ -58,7 +65,6 @@ typedef struct
   GstPad *sinkpad;
   GstElement *queue;
   GstElement *conv;
-  GstElement *resample;
   GstElement *volume;           /* element with the volume property */
   gboolean sink_volume;         /* if the volume was provided by the sink */
   GstElement *mute;             /* element with the mute property */
@@ -80,7 +86,6 @@ typedef struct
   GstPad *sinkpad;
   GstElement *queue;
   GstElement *conv;
-  GstElement *scale;
   GstElement *sink;
   gboolean async;
   GstElement *ts_offset;
@@ -126,6 +131,19 @@ typedef struct
   g_static_rec_mutex_unlock (GST_PLAY_SINK_GET_LOCK (playsink)); \
 } G_STMT_END
 
+#define PENDING_FLAG_SET(playsink, flagtype) \
+  ((playsink->pending_blocked_pads) |= (1 << flagtype))
+#define PENDING_FLAG_UNSET(playsink, flagtype) \
+  ((playsink->pending_blocked_pads) &= ~(1 << flagtype))
+#define PENDING_FLAG_IS_SET(playsink, flagtype) \
+  ((playsink->pending_blocked_pads) & (1 << flagtype))
+#define PENDING_VIDEO_BLOCK(playsink) \
+  ((playsink->pending_blocked_pads) & (1 << GST_PLAY_SINK_TYPE_VIDEO_RAW | 1 << GST_PLAY_SINK_TYPE_VIDEO))
+#define PENDING_AUDIO_BLOCK(playsink) \
+  ((playsink->pending_blocked_pads) & (1 << GST_PLAY_SINK_TYPE_AUDIO_RAW | 1 << GST_PLAY_SINK_TYPE_AUDIO))
+#define PENDING_TEXT_BLOCK(playsink) \
+  PENDING_FLAG_IS_SET(playsink, GST_PLAY_SINK_TYPE_TEXT)
+
 struct _GstPlaySink
 {
   GstBin bin;
@@ -149,6 +167,7 @@ struct _GstPlaySink
   /* audio */
   GstPad *audio_pad;
   gboolean audio_pad_raw;
+  gboolean audio_pad_blocked;
   GstPad *audio_srcpad_stream_synchronizer;
   GstPad *audio_sinkpad_stream_synchronizer;
   /* audio tee */
@@ -159,13 +178,17 @@ struct _GstPlaySink
   /* video */
   GstPad *video_pad;
   gboolean video_pad_raw;
+  gboolean video_pad_blocked;
   GstPad *video_srcpad_stream_synchronizer;
   GstPad *video_sinkpad_stream_synchronizer;
   /* text */
   GstPad *text_pad;
+  gboolean text_pad_blocked;
   GstPad *text_srcpad_stream_synchronizer;
   GstPad *text_sinkpad_stream_synchronizer;
 
+  guint32 pending_blocked_pads;
+
   /* properties */
   GstElement *audio_sink;
   GstElement *video_sink;
@@ -191,31 +214,35 @@ struct _GstPlaySinkClass
   GstBuffer *(*convert_frame) (GstPlaySink * playsink, GstCaps * caps);
 };
 
-static GstStaticPadTemplate audiorawtemplate =
-GST_STATIC_PAD_TEMPLATE ("audio_raw_sink",
+
+static GstStaticPadTemplate audiotemplate =
+GST_STATIC_PAD_TEMPLATE ("audio_sink",
     GST_PAD_SINK,
     GST_PAD_REQUEST,
     GST_STATIC_CAPS_ANY);
-static GstStaticPadTemplate audiotemplate =
-GST_STATIC_PAD_TEMPLATE ("audio_sink",
+static GstStaticPadTemplate videotemplate =
+GST_STATIC_PAD_TEMPLATE ("video_sink",
     GST_PAD_SINK,
     GST_PAD_REQUEST,
     GST_STATIC_CAPS_ANY);
-static GstStaticPadTemplate videorawtemplate =
-GST_STATIC_PAD_TEMPLATE ("video_raw_sink",
+static GstStaticPadTemplate texttemplate = GST_STATIC_PAD_TEMPLATE ("text_sink",
     GST_PAD_SINK,
     GST_PAD_REQUEST,
     GST_STATIC_CAPS_ANY);
-static GstStaticPadTemplate videotemplate =
-GST_STATIC_PAD_TEMPLATE ("video_sink",
+
+/* FIXME 0.11: Remove */
+static GstStaticPadTemplate audiorawtemplate =
+GST_STATIC_PAD_TEMPLATE ("audio_raw_sink",
     GST_PAD_SINK,
     GST_PAD_REQUEST,
     GST_STATIC_CAPS_ANY);
-static GstStaticPadTemplate texttemplate = GST_STATIC_PAD_TEMPLATE ("text_sink",
+static GstStaticPadTemplate videorawtemplate =
+GST_STATIC_PAD_TEMPLATE ("video_raw_sink",
     GST_PAD_SINK,
     GST_PAD_REQUEST,
     GST_STATIC_CAPS_ANY);
 
+
 /* props */
 enum
 {
@@ -228,6 +255,9 @@ enum
   PROP_VIS_PLUGIN,
   PROP_FRAME,
   PROP_AV_OFFSET,
+  PROP_VIDEO_SINK,
+  PROP_AUDIO_SINK,
+  PROP_TEXT_SINK,
   PROP_LAST
 };
 
@@ -383,6 +413,44 @@ gst_play_sink_class_init (GstPlaySinkClass * klass)
           G_MININT64, G_MAXINT64, 0,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  /**
+   * GstPlaySink:video-sink:
+   *
+   * Set the used video sink element. NULL will use the default sink. playsink
+   * must be in %GST_STATE_NULL
+   *
+   * Since: 0.10.36
+   */
+  g_object_class_install_property (gobject_klass, PROP_VIDEO_SINK,
+      g_param_spec_object ("video-sink", "Video Sink",
+          "the video output element to use (NULL = default sink)",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  /**
+   * GstPlaySink:audio-sink:
+   *
+   * Set the used audio sink element. NULL will use the default sink. playsink
+   * must be in %GST_STATE_NULL
+   *
+   * Since: 0.10.36
+   */
+  g_object_class_install_property (gobject_klass, PROP_AUDIO_SINK,
+      g_param_spec_object ("audio-sink", "Audio Sink",
+          "the audio output element to use (NULL = default sink)",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  /**
+   * GstPlaySink:text-sink:
+   *
+   * Set the used text sink element. NULL will use the default sink. playsink
+   * must be in %GST_STATE_NULL
+   *
+   * Since: 0.10.36
+   */
+  g_object_class_install_property (gobject_klass, PROP_TEXT_SINK,
+      g_param_spec_object ("text-sink", "Text sink",
+          "the text output element to use (NULL = default textoverlay)",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+
   g_signal_new ("reconfigure", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstPlaySinkClass,
           reconfigure), NULL, NULL, gst_marshal_BOOLEAN__VOID, G_TYPE_BOOLEAN,
@@ -409,16 +477,16 @@ gst_play_sink_class_init (GstPlaySinkClass * klass)
       G_STRUCT_OFFSET (GstPlaySinkClass, convert_frame), NULL, NULL,
       gst_play_marshal_BUFFER__BOXED, GST_TYPE_BUFFER, 1, GST_TYPE_CAPS);
 
-  gst_element_class_add_pad_template (gstelement_klass,
-      gst_static_pad_template_get (&audiorawtemplate));
-  gst_element_class_add_pad_template (gstelement_klass,
-      gst_static_pad_template_get (&audiotemplate));
-  gst_element_class_add_pad_template (gstelement_klass,
-      gst_static_pad_template_get (&videorawtemplate));
-  gst_element_class_add_pad_template (gstelement_klass,
-      gst_static_pad_template_get (&videotemplate));
-  gst_element_class_add_pad_template (gstelement_klass,
-      gst_static_pad_template_get (&texttemplate));
+  gst_element_class_add_static_pad_template (gstelement_klass,
+      &audiorawtemplate);
+  gst_element_class_add_static_pad_template (gstelement_klass,
+      &audiotemplate);
+  gst_element_class_add_static_pad_template (gstelement_klass,
+      &videorawtemplate);
+  gst_element_class_add_static_pad_template (gstelement_klass,
+      &videotemplate);
+  gst_element_class_add_static_pad_template (gstelement_klass,
+      &texttemplate);
   gst_element_class_set_details_simple (gstelement_klass, "Player Sink",
       "Generic/Bin/Sink",
       "Convenience sink for multiple streams",
@@ -613,6 +681,7 @@ gst_play_sink_get_sink (GstPlaySink * playsink, GstPlaySinkType type)
   GST_PLAY_SINK_LOCK (playsink);
   switch (type) {
     case GST_PLAY_SINK_TYPE_AUDIO:
+    case GST_PLAY_SINK_TYPE_AUDIO_RAW:
     {
       GstPlayAudioChain *chain;
       if ((chain = (GstPlayAudioChain *) playsink->audiochain))
@@ -621,6 +690,7 @@ gst_play_sink_get_sink (GstPlaySink * playsink, GstPlaySinkType type)
       break;
     }
     case GST_PLAY_SINK_TYPE_VIDEO:
+    case GST_PLAY_SINK_TYPE_VIDEO_RAW:
     {
       GstPlayVideoChain *chain;
       if ((chain = (GstPlayVideoChain *) playsink->videochain))
@@ -1077,7 +1147,7 @@ try_element (GstPlaySink * playsink, GstElement * element, gboolean unref)
 }
 
 /* make the element (bin) that contains the elements needed to perform
- * video display.
+ * video display. Only used for *raw* video streams.
  *
  *  +------------------------------------------------------------+
  *  | vbin                                                       |
@@ -1108,13 +1178,13 @@ gen_video_deinterlace_chain (GstPlaySink * playsink)
   bin = GST_BIN_CAST (chain->chain.bin);
   gst_object_ref_sink (bin);
 
-  GST_DEBUG_OBJECT (playsink, "creating ffmpegcolorspace");
-  chain->conv = gst_element_factory_make ("ffmpegcolorspace", "vdconv");
+  GST_DEBUG_OBJECT (playsink, "creating " COLORSPACE);
+  chain->conv = gst_element_factory_make (COLORSPACE, "vdconv");
   if (chain->conv == NULL) {
-    post_missing_element_message (playsink, "ffmpegcolorspace");
+    post_missing_element_message (playsink, COLORSPACE);
     GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
         (_("Missing element '%s' - check your GStreamer installation."),
-            "ffmpegcolorspace"), ("video rendering might fail"));
+            COLORSPACE), ("video rendering might fail"));
   } else {
     gst_bin_add (bin, chain->conv);
     head = chain->conv;
@@ -1170,6 +1240,22 @@ link_failed:
   }
 }
 
+#ifdef GST_EXT_LINK_FIMCCONVERT
+static gint
+compare_name (GstElement * element, const gchar * name)
+{
+  gchar *str;
+  GST_OBJECT_LOCK (element);
+  str = g_strrstr(GST_ELEMENT_NAME (element), name);
+  GST_OBJECT_UNLOCK (element);
+
+  if (str)
+    return 0;
+  else
+    return -1;
+}
+#endif
+
 /* make the element (bin) that contains the elements needed to perform
  * video display.
  *
@@ -1190,6 +1276,7 @@ gen_video_chain (GstPlaySink * playsink, gboolean raw, gboolean async)
   GstBin *bin;
   GstPad *pad;
   GstElement *head = NULL, *prev = NULL, *elem = NULL;
+  GstPlugin *p = NULL;
 
   chain = g_new0 (GstPlayVideoChain, 1);
   chain->chain.playsink = playsink;
@@ -1238,9 +1325,9 @@ gen_video_chain (GstPlaySink * playsink, gboolean raw, gboolean async)
   }
 
   /* find ts-offset element */
-  chain->ts_offset =
+  gst_object_replace ((GstObject **) & chain->ts_offset, (GstObject *)
       gst_play_sink_find_property_sinks (playsink, chain->sink, "ts-offset",
-      G_TYPE_INT64);
+          G_TYPE_INT64));
 
   /* create a bin to hold objects, as we create them we add them to this bin so
    * that when something goes wrong we only need to unref the bin */
@@ -1249,6 +1336,7 @@ gen_video_chain (GstPlaySink * playsink, gboolean raw, gboolean async)
   gst_object_ref_sink (bin);
   gst_bin_add (bin, chain->sink);
 
+#ifndef GST_EXT_XV_ENHANCEMENT
   /* decouple decoder from sink, this improves playback quite a lot since the
    * decoder can continue while the sink blocks for synchronisation. We don't
    * need a lot of buffers as this consumes a lot of memory and we don't want
@@ -1267,47 +1355,31 @@ gen_video_chain (GstPlaySink * playsink, gboolean raw, gboolean async)
     gst_bin_add (bin, chain->queue);
     head = prev = chain->queue;
   }
+#endif
 
-  if (raw && !(playsink->flags & GST_PLAY_FLAG_NATIVE_VIDEO)) {
-    GST_DEBUG_OBJECT (playsink, "creating ffmpegcolorspace");
-    chain->conv = gst_element_factory_make ("ffmpegcolorspace", "vconv");
-    if (chain->conv == NULL) {
-      post_missing_element_message (playsink, "ffmpegcolorspace");
-      GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
-          (_("Missing element '%s' - check your GStreamer installation."),
-              "ffmpegcolorspace"), ("video rendering might fail"));
-    } else {
-      gst_bin_add (bin, chain->conv);
-      if (prev) {
-        if (!gst_element_link_pads_full (prev, "src", chain->conv, "sink",
-                GST_PAD_LINK_CHECK_TEMPLATE_CAPS))
-          goto link_failed;
-      } else {
-        head = chain->conv;
-      }
-      prev = chain->conv;
-    }
-
-    GST_DEBUG_OBJECT (playsink, "creating videoscale");
-    chain->scale = gst_element_factory_make ("videoscale", "vscale");
-    if (chain->scale == NULL) {
-      post_missing_element_message (playsink, "videoscale");
-      GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
-          (_("Missing element '%s' - check your GStreamer installation."),
-              "videoscale"), ("possibly a liboil version mismatch?"));
+  if (!(playsink->flags & GST_PLAY_FLAG_NATIVE_VIDEO)) {
+    GST_DEBUG_OBJECT (playsink, "creating videoconverter");
+#ifdef GST_EXT_LINK_FIMCCONVERT
+    GstIterator *iter = gst_bin_iterate_recurse (gst_element_get_parent(playsink));
+    gpointer e = gst_iterator_find_custom (iter, (GCompareFunc) compare_name, "omx");
+    gst_iterator_free (iter);
+    if (e) {
+      chain->conv = gst_element_factory_make ("fimcconvert", "vconv");
+      gst_object_unref(p);
+    } else
+      chain->conv = gst_element_factory_make (COLORSPACE, "vconv");
+#else
+       chain->conv = g_object_new (GST_TYPE_PLAY_SINK_VIDEO_CONVERT, "name", "vconv", NULL);
+#endif
+    gst_bin_add (bin, chain->conv);
+    if (prev) {
+      if (!gst_element_link_pads_full (prev, "src", chain->conv, "sink",
+              GST_PAD_LINK_CHECK_TEMPLATE_CAPS))
+        goto link_failed;
     } else {
-      /* Add black borders if necessary to keep the DAR */
-      g_object_set (chain->scale, "add-borders", TRUE, NULL);
-      gst_bin_add (bin, chain->scale);
-      if (prev) {
-        if (!gst_element_link_pads_full (prev, "src", chain->scale, "sink",
-                GST_PAD_LINK_CHECK_TEMPLATE_CAPS))
-          goto link_failed;
-      } else {
-        head = chain->scale;
-      }
-      prev = chain->scale;
+      head = chain->conv;
     }
+    prev = chain->conv;
   }
 
   if (prev) {
@@ -1378,22 +1450,22 @@ setup_video_chain (GstPlaySink * playsink, gboolean raw, gboolean async)
 
   chain = playsink->videochain;
 
+  chain->chain.raw = raw;
+
   /* if the chain was active we don't do anything */
   if (GST_PLAY_CHAIN (chain)->activated == TRUE)
     return TRUE;
 
-  if (chain->chain.raw != raw)
-    return FALSE;
-
   /* try to set the sink element to READY again */
   ret = gst_element_set_state (chain->sink, GST_STATE_READY);
   if (ret == GST_STATE_CHANGE_FAILURE)
     return FALSE;
 
   /* find ts-offset element */
-  chain->ts_offset =
+
+  gst_object_replace ((GstObject **) & chain->ts_offset, (GstObject *)
       gst_play_sink_find_property_sinks (playsink, chain->sink, "ts-offset",
-      G_TYPE_INT64);
+          G_TYPE_INT64));
 
   /* if we can disable async behaviour of the sink, we can avoid adding a
    * queue for the audio chain. */
@@ -1413,6 +1485,7 @@ setup_video_chain (GstPlaySink * playsink, gboolean raw, gboolean async)
 }
 
 /* make an element for playback of video with subtitles embedded.
+ * Only used for *raw* video streams.
  *
  *  +--------------------------------------------+
  *  | tbin                                       |
@@ -1486,7 +1559,7 @@ gen_text_chain (GstPlaySink * playsink)
           chain->queue = NULL;
         }
         /* try to set sync to true but it's no biggie when we can't */
-        if ((elem =
+        if (chain->sink && (elem =
                 gst_play_sink_find_property_sinks (playsink, chain->sink,
                     "sync", G_TYPE_BOOLEAN)))
           g_object_set (elem, "sync", TRUE, NULL);
@@ -1559,10 +1632,18 @@ gen_text_chain (GstPlaySink * playsink)
               "max-size-bytes", 0, "max-size-time", (gint64) 0,
               "silent", TRUE, NULL);
           gst_bin_add (bin, element);
-          gst_element_link_pads_full (element, "src", chain->overlay,
-              "subtitle_sink", GST_PAD_LINK_CHECK_TEMPLATE_CAPS);
-          textsinkpad = gst_element_get_static_pad (element, "sink");
-          srcpad = gst_element_get_static_pad (chain->overlay, "src");
+          if (gst_element_link_pads_full (element, "src", chain->overlay,
+                  "subtitle_sink", GST_PAD_LINK_CHECK_TEMPLATE_CAPS)) {
+            textsinkpad = gst_element_get_static_pad (element, "sink");
+            srcpad = gst_element_get_static_pad (chain->overlay, "src");
+          } else {
+            gst_bin_remove (bin, chain->sink);
+            gst_bin_remove (bin, chain->overlay);
+            chain->sink = NULL;
+            chain->overlay = NULL;
+            gst_object_unref (videosinkpad);
+            videosinkpad = NULL;
+          }
         }
       }
     }
@@ -1707,9 +1788,9 @@ gen_audio_chain (GstPlaySink * playsink, gboolean raw)
   }
 
   /* find ts-offset element */
-  chain->ts_offset =
+  gst_object_replace ((GstObject **) & chain->ts_offset, (GstObject *)
       gst_play_sink_find_property_sinks (playsink, chain->sink, "ts-offset",
-      G_TYPE_INT64);
+          G_TYPE_INT64));
 
   /* check if the sink, or something within the sink, has the volume property.
    * If it does we don't need to add a volume element.  */
@@ -1757,54 +1838,33 @@ gen_audio_chain (GstPlaySink * playsink, gboolean raw)
     chain->sink_volume = FALSE;
   }
 
-  if (raw && !(playsink->flags & GST_PLAY_FLAG_NATIVE_AUDIO)) {
-    GST_DEBUG_OBJECT (playsink, "creating audioconvert");
-    chain->conv = gst_element_factory_make ("audioconvert", "aconv");
-    if (chain->conv == NULL) {
-      post_missing_element_message (playsink, "audioconvert");
-      GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
-          (_("Missing element '%s' - check your GStreamer installation."),
-              "audioconvert"), ("possibly a liboil version mismatch?"));
-    } else {
-      gst_bin_add (bin, chain->conv);
-      if (prev) {
-        if (!gst_element_link_pads_full (prev, "src", chain->conv, "sink",
-                GST_PAD_LINK_CHECK_TEMPLATE_CAPS))
-          goto link_failed;
-      } else {
-        head = chain->conv;
-      }
-      prev = chain->conv;
-    }
-
-    GST_DEBUG_OBJECT (playsink, "creating audioresample");
-    chain->resample = gst_element_factory_make ("audioresample", "aresample");
-    if (chain->resample == NULL) {
-      post_missing_element_message (playsink, "audioresample");
-      GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
-          (_("Missing element '%s' - check your GStreamer installation."),
-              "audioresample"), ("possibly a liboil version mismatch?"));
+  if (!(playsink->flags & GST_PLAY_FLAG_NATIVE_AUDIO) || (!have_volume
+          && playsink->flags & GST_PLAY_FLAG_SOFT_VOLUME)) {
+    gboolean use_converters = !(playsink->flags & GST_PLAY_FLAG_NATIVE_AUDIO);
+    gboolean use_volume =
+        !have_volume && playsink->flags & GST_PLAY_FLAG_SOFT_VOLUME;
+    GST_DEBUG_OBJECT (playsink,
+        "creating audioconvert with use-converters %d, use-volume %d",
+        use_converters, use_volume);
+    chain->conv =
+        g_object_new (GST_TYPE_PLAY_SINK_AUDIO_CONVERT, "name", "aconv",
+        "use-converters", use_converters, "use-volume", use_volume, NULL);
+    gst_bin_add (bin, chain->conv);
+    if (prev) {
+      if (!gst_element_link_pads_full (prev, "src", chain->conv, "sink",
+              GST_PAD_LINK_CHECK_TEMPLATE_CAPS))
+        goto link_failed;
     } else {
-      gst_bin_add (bin, chain->resample);
-      if (prev) {
-        if (!gst_element_link_pads_full (prev, "src", chain->resample, "sink",
-                GST_PAD_LINK_CHECK_TEMPLATE_CAPS))
-          goto link_failed;
-      } else {
-        head = chain->resample;
-      }
-      prev = chain->resample;
+      head = chain->conv;
     }
+    prev = chain->conv;
 
     if (!have_volume && playsink->flags & GST_PLAY_FLAG_SOFT_VOLUME) {
-      GST_DEBUG_OBJECT (playsink, "creating volume");
-      chain->volume = gst_element_factory_make ("volume", "volume");
-      if (chain->volume == NULL) {
-        post_missing_element_message (playsink, "volume");
-        GST_ELEMENT_WARNING (playsink, CORE, MISSING_PLUGIN,
-            (_("Missing element '%s' - check your GStreamer installation."),
-                "volume"), ("possibly a liboil version mismatch?"));
-      } else {
+      GstPlaySinkAudioConvert *conv =
+          GST_PLAY_SINK_AUDIO_CONVERT_CAST (chain->conv);
+
+      if (conv->volume) {
+        chain->volume = conv->volume;
         have_volume = TRUE;
 
         g_signal_connect (chain->volume, "notify::volume",
@@ -1819,16 +1879,6 @@ gen_audio_chain (GstPlaySink * playsink, gboolean raw)
         g_object_set (G_OBJECT (chain->volume), "volume", playsink->volume,
             NULL);
         g_object_set (G_OBJECT (chain->mute), "mute", playsink->mute, NULL);
-        gst_bin_add (bin, chain->volume);
-
-        if (prev) {
-          if (!gst_element_link_pads_full (prev, "src", chain->volume, "sink",
-                  GST_PAD_LINK_CHECK_TEMPLATE_CAPS))
-            goto link_failed;
-        } else {
-          head = chain->volume;
-        }
-        prev = chain->volume;
       }
     }
   }
@@ -1910,22 +1960,21 @@ setup_audio_chain (GstPlaySink * playsink, gboolean raw)
 
   chain = playsink->audiochain;
 
+  chain->chain.raw = raw;
+
   /* if the chain was active we don't do anything */
   if (GST_PLAY_CHAIN (chain)->activated == TRUE)
     return TRUE;
 
-  if (chain->chain.raw != raw)
-    return FALSE;
-
   /* try to set the sink element to READY again */
   ret = gst_element_set_state (chain->sink, GST_STATE_READY);
   if (ret == GST_STATE_CHANGE_FAILURE)
     return FALSE;
 
   /* find ts-offset element */
-  chain->ts_offset =
+  gst_object_replace ((GstObject **) & chain->ts_offset, (GstObject *)
       gst_play_sink_find_property_sinks (playsink, chain->sink, "ts-offset",
-      G_TYPE_INT64);
+          G_TYPE_INT64));
 
   /* check if the sink, or something within the sink, has the volume property.
    * If it does we don't need to add a volume element.  */
@@ -1956,29 +2005,35 @@ setup_audio_chain (GstPlaySink * playsink, gboolean raw)
       g_signal_connect (chain->mute, "notify::mute",
           G_CALLBACK (notify_mute_cb), playsink);
     }
+
+    g_object_set (chain->conv, "use-volume", FALSE, NULL);
   } else {
+    GstPlaySinkAudioConvert *conv =
+        GST_PLAY_SINK_AUDIO_CONVERT_CAST (chain->conv);
+
     /* no volume, we need to add a volume element when we can */
+    g_object_set (chain->conv, "use-volume", TRUE, NULL);
     GST_DEBUG_OBJECT (playsink, "the sink has no volume property");
-    if (!raw) {
-      GST_LOG_OBJECT (playsink, "non-raw format, can't do soft volume control");
 
-      disconnect_chain (chain, playsink);
-      chain->volume = NULL;
-      chain->mute = NULL;
-    } else {
-      /* both last and current chain are raw audio, there should be a volume
-       * element already, unless the sink changed from one with a volume
-       * property to one that hasn't got a volume property, in which case we
-       * re-generate the chain */
-      if (chain->volume == NULL) {
-        GST_DEBUG_OBJECT (playsink, "no existing volume element to re-use");
-        /* undo background state change done earlier */
-        gst_element_set_state (chain->sink, GST_STATE_NULL);
-        return FALSE;
-      }
+    /* Disconnect signals */
+    disconnect_chain (chain, playsink);
+
+    if (conv->volume) {
+      chain->volume = conv->volume;
+      chain->mute = chain->volume;
 
-      GST_DEBUG_OBJECT (playsink, "reusing existing volume element");
+      g_signal_connect (chain->volume, "notify::volume",
+          G_CALLBACK (notify_volume_cb), playsink);
+
+      g_signal_connect (chain->mute, "notify::mute",
+          G_CALLBACK (notify_mute_cb), playsink);
+
+      /* configure with the latest volume and mute */
+      g_object_set (G_OBJECT (chain->volume), "volume", playsink->volume, NULL);
+      g_object_set (G_OBJECT (chain->mute), "mute", playsink->mute, NULL);
     }
+
+    GST_DEBUG_OBJECT (playsink, "reusing existing volume element");
   }
   return TRUE;
 }
@@ -2331,6 +2386,8 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
 
       add_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
       activate_chain (GST_PLAY_CHAIN (playsink->videochain), FALSE);
+      if (playsink->videochain->ts_offset)
+        gst_object_unref (playsink->videochain->ts_offset);
       playsink->videochain->ts_offset = NULL;
     }
 
@@ -2384,6 +2441,8 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
         disconnect_chain (playsink->audiochain, playsink);
         playsink->audiochain->volume = NULL;
         playsink->audiochain->mute = NULL;
+        if (playsink->audiochain->ts_offset)
+          gst_object_unref (playsink->audiochain->ts_offset);
         playsink->audiochain->ts_offset = NULL;
         free_chain ((GstPlayChain *) playsink->audiochain);
         playsink->audiochain = NULL;
@@ -2452,6 +2511,8 @@ gst_play_sink_reconfigure (GstPlaySink * playsink)
         disconnect_chain (playsink->audiochain, playsink);
         playsink->audiochain->volume = NULL;
         playsink->audiochain->mute = NULL;
+        if (playsink->audiochain->ts_offset)
+          gst_object_unref (playsink->audiochain->ts_offset);
         playsink->audiochain->ts_offset = NULL;
       }
       add_chain (GST_PLAY_CHAIN (playsink->audiochain), FALSE);
@@ -2843,12 +2904,221 @@ gst_play_sink_convert_frame (GstPlaySink * playsink, GstCaps * caps)
        * on the bus or not. It's not like it's a critical issue regarding
        * playsink behaviour. */
       GST_ERROR ("Error converting frame: %s", err->message);
+      g_error_free (err);
     }
     result = temp;
   }
   return result;
 }
 
+static gboolean
+is_raw_structure (GstStructure * s)
+{
+  const gchar *name;
+
+  name = gst_structure_get_name (s);
+
+  if (g_str_has_prefix (name, "video/x-raw-") ||
+      g_str_has_prefix (name, "audio/x-raw-"))
+    return TRUE;
+  return FALSE;
+}
+
+static gboolean
+is_raw_pad (GstPad * pad)
+{
+  GstPad *peer = gst_pad_get_peer (pad);
+  GstCaps *caps;
+  gboolean raw = TRUE;
+
+  if (!peer)
+    return raw;
+
+  caps = gst_pad_get_negotiated_caps (peer);
+  if (!caps) {
+    guint i, n;
+
+    caps = gst_pad_get_caps_reffed (peer);
+
+    n = gst_caps_get_size (caps);
+    for (i = 0; i < n; i++) {
+      gboolean r = is_raw_structure (gst_caps_get_structure (caps, i));
+
+      if (i == 0) {
+        raw = r;
+      } else if (raw != r) {
+        GST_ERROR_OBJECT (pad,
+            "Caps contains raw and non-raw structures: %" GST_PTR_FORMAT, caps);
+        raw = FALSE;
+        break;
+      }
+    }
+  } else {
+    raw = is_raw_structure (gst_caps_get_structure (caps, 0));
+  }
+  gst_caps_unref (caps);
+  gst_object_unref (peer);
+
+  return raw;
+}
+
+static void
+sinkpad_blocked_cb (GstPad * blockedpad, gboolean blocked, gpointer user_data)
+{
+  GstPlaySink *playsink = (GstPlaySink *) user_data;
+  GstPad *pad;
+
+  GST_PLAY_SINK_LOCK (playsink);
+
+  pad = GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD (blockedpad)));
+  if (pad == playsink->video_pad) {
+    playsink->video_pad_blocked = blocked;
+    GST_DEBUG_OBJECT (pad, "Video pad blocked: %d", blocked);
+    if (!blocked) {
+      PENDING_FLAG_UNSET (playsink, GST_PLAY_SINK_TYPE_VIDEO_RAW);
+      PENDING_FLAG_UNSET (playsink, GST_PLAY_SINK_TYPE_VIDEO);
+    }
+  } else if (pad == playsink->audio_pad) {
+    playsink->audio_pad_blocked = blocked;
+    GST_DEBUG_OBJECT (pad, "Audio pad blocked: %d", blocked);
+    if (!blocked) {
+      PENDING_FLAG_UNSET (playsink, GST_PLAY_SINK_TYPE_AUDIO_RAW);
+      PENDING_FLAG_UNSET (playsink, GST_PLAY_SINK_TYPE_AUDIO);
+    }
+  } else if (pad == playsink->text_pad) {
+    playsink->text_pad_blocked = blocked;
+    GST_DEBUG_OBJECT (pad, "Text pad blocked: %d", blocked);
+    if (!blocked)
+      PENDING_FLAG_UNSET (playsink, GST_PLAY_SINK_TYPE_TEXT);
+  }
+
+  if (!blocked) {
+    gst_object_unref (pad);
+    GST_PLAY_SINK_UNLOCK (playsink);
+    return;
+  }
+
+  /* We reconfigure when for ALL streams:
+   * * there isn't a pad
+   * * OR the pad is blocked
+   * * OR there are no pending blocks on that pad
+   */
+
+  if ((!playsink->video_pad || playsink->video_pad_blocked
+          || !PENDING_VIDEO_BLOCK (playsink)) && (!playsink->audio_pad
+          || playsink->audio_pad_blocked || !PENDING_AUDIO_BLOCK (playsink))
+      && (!playsink->text_pad || playsink->text_pad_blocked
+          || !PENDING_TEXT_BLOCK (playsink))) {
+    GST_DEBUG_OBJECT (playsink, "All pads blocked -- reconfiguring");
+
+    if (playsink->video_pad) {
+      playsink->video_pad_raw = is_raw_pad (playsink->video_pad);
+      GST_DEBUG_OBJECT (playsink, "Video pad is raw: %d",
+          playsink->video_pad_raw);
+    }
+
+    if (playsink->audio_pad) {
+      playsink->audio_pad_raw = is_raw_pad (playsink->audio_pad);
+      GST_DEBUG_OBJECT (playsink, "Audio pad is raw: %d",
+          playsink->audio_pad_raw);
+    }
+
+    gst_play_sink_reconfigure (playsink);
+
+    if (playsink->video_pad) {
+      GstPad *opad =
+          GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD
+              (playsink->video_pad)));
+      gst_pad_set_blocked_async_full (opad, FALSE, sinkpad_blocked_cb,
+          gst_object_ref (playsink), (GDestroyNotify) gst_object_unref);
+      gst_object_unref (opad);
+    }
+
+    if (playsink->audio_pad) {
+      GstPad *opad =
+          GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD
+              (playsink->audio_pad)));
+      gst_pad_set_blocked_async_full (opad, FALSE, sinkpad_blocked_cb,
+          gst_object_ref (playsink), (GDestroyNotify) gst_object_unref);
+      gst_object_unref (opad);
+    }
+
+    if (playsink->text_pad) {
+      GstPad *opad =
+          GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD
+              (playsink->text_pad)));
+      gst_pad_set_blocked_async_full (opad, FALSE, sinkpad_blocked_cb,
+          gst_object_ref (playsink), (GDestroyNotify) gst_object_unref);
+      gst_object_unref (opad);
+    }
+  }
+
+  gst_object_unref (pad);
+
+  GST_PLAY_SINK_UNLOCK (playsink);
+}
+
+static void
+caps_notify_cb (GstPad * pad, GParamSpec * unused, GstPlaySink * playsink)
+{
+  gboolean reconfigure = FALSE;
+  GstCaps *caps;
+  gboolean raw;
+
+  g_object_get (pad, "caps", &caps, NULL);
+  if (!caps)
+    return;
+
+  if (pad == playsink->audio_pad) {
+    raw = is_raw_pad (pad);
+    reconfigure = (!!playsink->audio_pad_raw != !!raw)
+        && playsink->audiochain;
+    GST_DEBUG_OBJECT (pad,
+        "Audio caps changed: raw %d reconfigure %d caps %" GST_PTR_FORMAT, raw,
+        reconfigure, caps);
+  } else if (pad == playsink->video_pad) {
+    raw = is_raw_pad (pad);
+    reconfigure = (!!playsink->video_pad_raw != !!raw)
+        && playsink->videochain;
+    GST_DEBUG_OBJECT (pad,
+        "Video caps changed: raw %d reconfigure %d caps %" GST_PTR_FORMAT, raw,
+        reconfigure, caps);
+  }
+
+  gst_caps_unref (caps);
+
+  if (reconfigure) {
+    GST_PLAY_SINK_LOCK (playsink);
+    if (playsink->video_pad) {
+      GstPad *opad =
+          GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD
+              (playsink->video_pad)));
+      gst_pad_set_blocked_async_full (opad, TRUE, sinkpad_blocked_cb,
+          gst_object_ref (playsink), (GDestroyNotify) gst_object_unref);
+      gst_object_unref (opad);
+    }
+
+    if (playsink->audio_pad) {
+      GstPad *opad =
+          GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD
+              (playsink->audio_pad)));
+      gst_pad_set_blocked_async_full (opad, TRUE, sinkpad_blocked_cb,
+          gst_object_ref (playsink), (GDestroyNotify) gst_object_unref);
+      gst_object_unref (opad);
+    }
+
+    if (playsink->text_pad) {
+      GstPad *opad =
+          GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD
+              (playsink->text_pad)));
+      gst_pad_set_blocked_async_full (opad, TRUE, sinkpad_blocked_cb,
+          gst_object_ref (playsink), (GDestroyNotify) gst_object_unref);
+      gst_object_unref (opad);
+    }
+    GST_PLAY_SINK_UNLOCK (playsink);
+  }
+}
+
 /**
  * gst_play_sink_request_pad
  * @playsink: a #GstPlaySink
@@ -2863,7 +3133,6 @@ gst_play_sink_request_pad (GstPlaySink * playsink, GstPlaySinkType type)
 {
   GstPad *res = NULL;
   gboolean created = FALSE;
-  gboolean raw = FALSE;
   gboolean activate = TRUE;
   const gchar *pad_name = NULL;
 
@@ -2872,11 +3141,8 @@ gst_play_sink_request_pad (GstPlaySink * playsink, GstPlaySinkType type)
   GST_PLAY_SINK_LOCK (playsink);
   switch (type) {
     case GST_PLAY_SINK_TYPE_AUDIO_RAW:
-      pad_name = "audio_raw_sink";
-      raw = TRUE;
     case GST_PLAY_SINK_TYPE_AUDIO:
-      if (pad_name == NULL)
-        pad_name = "audio_sink";
+      pad_name = "audio_sink";
       if (!playsink->audio_tee) {
         GST_LOG_OBJECT (playsink, "creating tee");
         /* create tee when needed. This element will feed the audio sink chain
@@ -2902,24 +3168,25 @@ gst_play_sink_request_pad (GstPlaySink * playsink, GstPlaySinkType type)
         GST_LOG_OBJECT (playsink, "ghosting tee sinkpad");
         playsink->audio_pad =
             gst_ghost_pad_new (pad_name, playsink->audio_tee_sink);
+        g_signal_connect (G_OBJECT (playsink->audio_pad), "notify::caps",
+            G_CALLBACK (caps_notify_cb), playsink);
         created = TRUE;
       }
-      playsink->audio_pad_raw = raw;
+      playsink->audio_pad_raw = FALSE;
       res = playsink->audio_pad;
       break;
     case GST_PLAY_SINK_TYPE_VIDEO_RAW:
-      pad_name = "video_raw_sink";
-      raw = TRUE;
     case GST_PLAY_SINK_TYPE_VIDEO:
-      if (pad_name == NULL)
-        pad_name = "video_sink";
+      pad_name = "video_sink";
       if (!playsink->video_pad) {
         GST_LOG_OBJECT (playsink, "ghosting videosink");
         playsink->video_pad =
             gst_ghost_pad_new_no_target (pad_name, GST_PAD_SINK);
+        g_signal_connect (G_OBJECT (playsink->video_pad), "notify::caps",
+            G_CALLBACK (caps_notify_cb), playsink);
         created = TRUE;
       }
-      playsink->video_pad_raw = raw;
+      playsink->video_pad_raw = FALSE;
       res = playsink->video_pad;
       break;
     case GST_PLAY_SINK_TYPE_TEXT:
@@ -2955,6 +3222,15 @@ gst_play_sink_request_pad (GstPlaySink * playsink, GstPlaySinkType type)
      * element is 'running' */
     gst_pad_set_active (res, TRUE);
     gst_element_add_pad (GST_ELEMENT_CAST (playsink), res);
+    if (type != GST_PLAY_SINK_TYPE_FLUSHING) {
+      GstPad *blockpad =
+          GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD (res)));
+
+      gst_pad_set_blocked_async_full (blockpad, TRUE, sinkpad_blocked_cb,
+          gst_object_ref (playsink), (GDestroyNotify) gst_object_unref);
+      PENDING_FLAG_SET (playsink, type);
+      gst_object_unref (blockpad);
+    }
     if (!activate)
       gst_pad_set_active (res, activate);
   }
@@ -3011,8 +3287,12 @@ gst_play_sink_release_pad (GstPlaySink * playsink, GstPad * pad)
   GST_PLAY_SINK_LOCK (playsink);
   if (pad == playsink->video_pad) {
     res = &playsink->video_pad;
+    g_signal_handlers_disconnect_by_func (playsink->video_pad, caps_notify_cb,
+        playsink);
   } else if (pad == playsink->audio_pad) {
     res = &playsink->audio_pad;
+    g_signal_handlers_disconnect_by_func (playsink->audio_pad, caps_notify_cb,
+        playsink);
   } else if (pad == playsink->text_pad) {
     res = &playsink->text_pad;
   } else {
@@ -3099,7 +3379,7 @@ gst_play_sink_send_event_to_sink (GstPlaySink * playsink, GstEvent * event)
   if (playsink->textchain && playsink->textchain->sink) {
     gst_event_ref (event);
     if ((res = gst_element_send_event (playsink->textchain->chain.bin, event))) {
-      GST_DEBUG_OBJECT (playsink, "Sent event succesfully to text sink");
+      GST_DEBUG_OBJECT (playsink, "Sent event successfully to text sink");
     } else {
       GST_DEBUG_OBJECT (playsink, "Event failed when sent to text sink");
     }
@@ -3108,7 +3388,7 @@ gst_play_sink_send_event_to_sink (GstPlaySink * playsink, GstEvent * event)
   if (playsink->videochain) {
     gst_event_ref (event);
     if ((res = gst_element_send_event (playsink->videochain->chain.bin, event))) {
-      GST_DEBUG_OBJECT (playsink, "Sent event succesfully to video sink");
+      GST_DEBUG_OBJECT (playsink, "Sent event successfully to video sink");
       goto done;
     }
     GST_DEBUG_OBJECT (playsink, "Event failed when sent to video sink");
@@ -3116,7 +3396,7 @@ gst_play_sink_send_event_to_sink (GstPlaySink * playsink, GstEvent * event)
   if (playsink->audiochain) {
     gst_event_ref (event);
     if ((res = gst_element_send_event (playsink->audiochain->chain.bin, event))) {
-      GST_DEBUG_OBJECT (playsink, "Sent event succesfully to audio sink");
+      GST_DEBUG_OBJECT (playsink, "Sent event successfully to audio sink");
       goto done;
     }
     GST_DEBUG_OBJECT (playsink, "Event failed when sent to audio sink");
@@ -3193,6 +3473,46 @@ gst_play_sink_change_state (GstElement * element, GstStateChange transition)
       ret = GST_STATE_CHANGE_ASYNC;
       break;
     case GST_STATE_CHANGE_PAUSED_TO_READY:
+      /* unblock all pads here */
+      GST_PLAY_SINK_LOCK (playsink);
+      if (playsink->video_pad) {
+        GstPad *opad =
+            GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD
+                (playsink->video_pad)));
+        if (gst_pad_is_blocked (opad)) {
+          gst_pad_set_blocked_async_full (opad, FALSE, sinkpad_blocked_cb,
+              gst_object_ref (playsink), (GDestroyNotify) gst_object_unref);
+        }
+        gst_object_unref (opad);
+        playsink->video_pad_blocked = FALSE;
+      }
+
+      if (playsink->audio_pad) {
+        GstPad *opad =
+            GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD
+                (playsink->audio_pad)));
+
+        if (gst_pad_is_blocked (opad)) {
+          gst_pad_set_blocked_async_full (opad, FALSE, sinkpad_blocked_cb,
+              gst_object_ref (playsink), (GDestroyNotify) gst_object_unref);
+        }
+        gst_object_unref (opad);
+        playsink->audio_pad_blocked = FALSE;
+      }
+
+      if (playsink->text_pad) {
+        GstPad *opad =
+            GST_PAD_CAST (gst_proxy_pad_get_internal (GST_PROXY_PAD
+                (playsink->text_pad)));
+        if (gst_pad_is_blocked (opad)) {
+          gst_pad_set_blocked_async_full (opad, FALSE, sinkpad_blocked_cb,
+              gst_object_ref (playsink), (GDestroyNotify) gst_object_unref);
+        }
+        gst_object_unref (opad);
+        playsink->text_pad_blocked = FALSE;
+      }
+      GST_PLAY_SINK_UNLOCK (playsink);
+      /* fall through */
     case GST_STATE_CHANGE_READY_TO_NULL:
       if (playsink->audiochain && playsink->audiochain->sink_volume) {
         /* remove our links to the mute and volume elements when they were
@@ -3200,8 +3520,17 @@ gst_play_sink_change_state (GstElement * element, GstStateChange transition)
         disconnect_chain (playsink->audiochain, playsink);
         playsink->audiochain->volume = NULL;
         playsink->audiochain->mute = NULL;
+      }
+
+      if (playsink->audiochain && playsink->audiochain->ts_offset) {
+        gst_object_unref (playsink->audiochain->ts_offset);
         playsink->audiochain->ts_offset = NULL;
       }
+
+      if (playsink->videochain && playsink->videochain->ts_offset) {
+        gst_object_unref (playsink->videochain->ts_offset);
+        playsink->videochain->ts_offset = NULL;
+      }
       ret = GST_STATE_CHANGE_SUCCESS;
       break;
     default:
@@ -3376,6 +3705,18 @@ gst_play_sink_set_property (GObject * object, guint prop_id,
     case PROP_AV_OFFSET:
       gst_play_sink_set_av_offset (playsink, g_value_get_int64 (value));
       break;
+    case PROP_VIDEO_SINK:
+      gst_play_sink_set_sink (playsink, GST_PLAY_SINK_TYPE_VIDEO,
+          g_value_get_object (value));
+      break;
+    case PROP_AUDIO_SINK:
+      gst_play_sink_set_sink (playsink, GST_PLAY_SINK_TYPE_AUDIO,
+          g_value_get_object (value));
+      break;
+    case PROP_TEXT_SINK:
+      gst_play_sink_set_sink (playsink, GST_PLAY_SINK_TYPE_TEXT,
+          g_value_get_object (value));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, spec);
       break;
@@ -3414,6 +3755,18 @@ gst_play_sink_get_property (GObject * object, guint prop_id,
     case PROP_AV_OFFSET:
       g_value_set_int64 (value, gst_play_sink_get_av_offset (playsink));
       break;
+    case PROP_VIDEO_SINK:
+      g_value_take_object (value, gst_play_sink_get_sink (playsink,
+              GST_PLAY_SINK_TYPE_VIDEO));
+      break;
+    case PROP_AUDIO_SINK:
+      g_value_take_object (value, gst_play_sink_get_sink (playsink,
+              GST_PLAY_SINK_TYPE_AUDIO));
+      break;
+    case PROP_TEXT_SINK:
+      g_value_take_object (value, gst_play_sink_get_sink (playsink,
+              GST_PLAY_SINK_TYPE_TEXT));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, spec);
       break;