Merge branch 'upstream/1.16' into tizen_gst_1.16.2
[platform/upstream/gst-plugins-good.git] / gst / flv / gstflvdemux.c
index 9e09867..283a76c 100644 (file)
@@ -13,8 +13,8 @@
  *
  * You should have received a copy of the GNU Library General Public
  * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
  */
 
 /**
@@ -25,7 +25,7 @@
  * <refsect2>
  * <title>Example launch line</title>
  * |[
- * gst-launch -v filesrc location=/path/to/flv ! flvdemux ! audioconvert ! autoaudiosink
+ * gst-launch-1.0 -v filesrc location=/path/to/flv ! flvdemux ! audioconvert ! autoaudiosink
  * ]| This pipeline demuxes an FLV file and outputs the contained raw audio streams.
  * </refsect2>
  */
 #include "gstflvmux.h"
 
 #include <string.h>
+#include <stdio.h>
 #include <gst/base/gstbytereader.h>
+#include <gst/base/gstbytewriter.h>
 #include <gst/pbutils/descriptions.h>
 #include <gst/pbutils/pbutils.h>
 #include <gst/audio/audio.h>
+#include <gst/video/video.h>
+#include <gst/tag/tag.h>
 
 /* FIXME: don't rely on own GstIndex */
 #include "gstindex.c"
@@ -66,16 +70,16 @@ static GstStaticPadTemplate audio_src_template =
         "audio/mpeg, mpegversion = (int) 4, stream-format = (string) raw, framed = (boolean) TRUE; "
         "audio/x-nellymoser, channels = (int) { 1, 2 }, rate = (int) { 5512, 8000, 11025, 16000, 22050, 44100 }; "
         "audio/x-raw, format = (string) { U8, S16LE }, layout = (string) interleaved, channels = (int) { 1, 2 }, rate = (int) { 5512, 11025, 22050, 44100 }; "
-        "audio/x-alaw, channels = (int) { 1, 2 }, rate = (int) { 5512, 11025, 22050, 44100 }; "
-        "audio/x-mulaw, channels = (int) { 1, 2 }, rate = (int) { 5512, 11025, 22050, 44100 }; "
-        "audio/x-speex, channels = (int) { 1, 2 }, rate = (int) { 5512, 11025, 22050, 44100 };")
+        "audio/x-alaw, channels = (int) { 1, 2 }, rate = (int) 8000; "
+        "audio/x-mulaw, channels = (int) { 1, 2 }, rate = (int) 8000; "
+        "audio/x-speex, channels = (int) 1, rate = (int) 16000;")
     );
 
 static GstStaticPadTemplate video_src_template =
     GST_STATIC_PAD_TEMPLATE ("video",
     GST_PAD_SRC,
     GST_PAD_SOMETIMES,
-    GST_STATIC_CAPS ("video/x-flash-video; "
+    GST_STATIC_CAPS ("video/x-flash-video, flvversion=(int) 1; "
         "video/x-flash-screen; "
         "video/x-vp6-flash; " "video/x-vp6-alpha; "
         "video/x-h264, stream-format=avc;")
@@ -92,9 +96,12 @@ G_DEFINE_TYPE (GstFlvDemux, gst_flv_demux, GST_TYPE_ELEMENT);
 /* 1 byte of tag type + 3 bytes of tag data size */
 #define FLV_TAG_TYPE_SIZE 4
 
-/* two seconds - consider pts are resynced to another base if this different */
+/* two seconds - consider dts are resynced to another base if this different */
 #define RESYNC_THRESHOLD 2000
 
+/* how much stream time to wait for audio tags to appear after we have video, or vice versa */
+#define NO_MORE_PADS_THRESHOLD (6 * GST_SECOND)
+
 static gboolean flv_demux_handle_seek_push (GstFlvDemux * demux,
     GstEvent * event);
 static gboolean gst_flv_demux_handle_seek_pull (GstFlvDemux * demux,
@@ -105,12 +112,16 @@ static gboolean gst_flv_demux_query (GstPad * pad, GstObject * parent,
 static gboolean gst_flv_demux_src_event (GstPad * pad, GstObject * parent,
     GstEvent * event);
 
+static GstIndex *gst_flv_demux_get_index (GstElement * element);
+
+static void gst_flv_demux_push_tags (GstFlvDemux * demux);
 
 static void
 gst_flv_demux_parse_and_add_index_entry (GstFlvDemux * demux, GstClockTime ts,
     guint64 pos, gboolean keyframe)
 {
   GstIndexAssociation associations[2];
+  GstIndex *index;
   GstIndexEntry *entry;
 
   GST_LOG_OBJECT (demux,
@@ -121,8 +132,13 @@ gst_flv_demux_parse_and_add_index_entry (GstFlvDemux * demux, GstClockTime ts,
   if (!demux->upstream_seekable)
     return;
 
+  index = gst_flv_demux_get_index (GST_ELEMENT (demux));
+
+  if (!index)
+    return;
+
   /* entry may already have been added before, avoid adding indefinitely */
-  entry = gst_index_get_assoc_entry (demux->index, demux->index_id,
+  entry = gst_index_get_assoc_entry (index, demux->index_id,
       GST_INDEX_LOOKUP_EXACT, GST_ASSOCIATION_FLAG_NONE, GST_FORMAT_BYTES, pos);
 
   if (entry) {
@@ -138,6 +154,7 @@ gst_flv_demux_parse_and_add_index_entry (GstFlvDemux * demux, GstClockTime ts,
     if (time != ts || key != ! !keyframe)
       GST_DEBUG_OBJECT (demux, "metadata mismatch");
 #endif
+    gst_object_unref (index);
     return;
   }
 
@@ -146,7 +163,7 @@ gst_flv_demux_parse_and_add_index_entry (GstFlvDemux * demux, GstClockTime ts,
   associations[1].format = GST_FORMAT_BYTES;
   associations[1].value = pos;
 
-  gst_index_add_associationv (demux->index, demux->index_id,
+  gst_index_add_associationv (index, demux->index_id,
       (keyframe) ? GST_ASSOCIATION_FLAG_KEY_UNIT :
       GST_ASSOCIATION_FLAG_DELTA_UNIT, 2,
       (const GstIndexAssociation *) &associations);
@@ -155,6 +172,8 @@ gst_flv_demux_parse_and_add_index_entry (GstFlvDemux * demux, GstClockTime ts,
     demux->index_max_pos = pos;
   if (ts > demux->index_max_time)
     demux->index_max_time = ts;
+
+  gst_object_unref (index);
 }
 
 static gchar *
@@ -227,55 +246,78 @@ gst_flv_demux_check_seekability (GstFlvDemux * demux)
   GST_DEBUG_OBJECT (demux, "upstream seekable: %d", demux->upstream_seekable);
 }
 
-static void
-parse_flv_demux_parse_date_string (GDate * date, const gchar * s)
+static GstDateTime *
+parse_flv_demux_parse_date_string (const gchar * s)
 {
-  g_date_set_parse (date, s);
-  if (g_date_valid (date))
-    return;
+  static const gchar months[12][4] = {
+    "Jan", "Feb", "Mar", "Apr",
+    "May", "Jun", "Jul", "Aug",
+    "Sep", "Oct", "Nov", "Dec"
+  };
+  GstDateTime *dt = NULL;
+  gchar **tokens;
+  guint64 d;
+  gchar *endptr, *stripped;
+  gint i, hh, mm, ss;
+  gint year = -1, month = -1, day = -1;
+  gint hour = -1, minute = -1, seconds = -1;
+
+  stripped = g_strstrip (g_strdup (s));
 
   /* "Fri Oct 15 15:13:16 2004" needs to be parsed */
-  {
-    static const gchar *months[] = {
-      "Jan", "Feb", "Mar", "Apr",
-      "May", "Jun", "Jul", "Aug",
-      "Sep", "Oct", "Nov", "Dec"
-    };
-    gchar **tokens = g_strsplit (s, " ", -1);
-    guint64 d;
-    gchar *endptr;
-    gint i;
-
-    if (g_strv_length (tokens) != 5)
-      goto out;
-
-    if (strlen (tokens[1]) != 3)
-      goto out;
-    for (i = 0; i < 12; i++) {
-      if (!strcmp (tokens[1], months[i])) {
-        break;
-      }
-    }
-    if (i == 12)
-      goto out;
-    g_date_set_month (date, i + 1);
+  tokens = g_strsplit (stripped, " ", -1);
+
+  g_free (stripped);
 
-    d = g_ascii_strtoull (tokens[2], &endptr, 10);
-    if (d == 0 && *endptr != '\0')
-      goto out;
+  if (g_strv_length (tokens) != 5)
+    goto out;
 
-    g_date_set_day (date, d);
+  /* year */
+  d = g_ascii_strtoull (tokens[4], &endptr, 10);
+  if (d == 0 && *endptr != '\0')
+    goto out;
 
-    d = g_ascii_strtoull (tokens[4], &endptr, 10);
-    if (d == 0 && *endptr != '\0')
-      goto out;
+  year = d;
 
-    g_date_set_year (date, d);
+  /* month */
+  if (strlen (tokens[1]) != 3)
+    goto out;
+  for (i = 0; i < 12; i++) {
+    if (!strcmp (tokens[1], months[i])) {
+      break;
+    }
+  }
+  if (i == 12)
+    goto out;
+
+  month = i + 1;
 
-  out:
-    if (tokens)
-      g_strfreev (tokens);
+  /* day */
+  d = g_ascii_strtoull (tokens[2], &endptr, 10);
+  if (d == 0 && *endptr != '\0')
+    goto out;
+
+  day = d;
+
+  /* time */
+  hh = mm = ss = 0;
+  if (sscanf (tokens[3], "%d:%d:%d", &hh, &mm, &ss) < 2)
+    goto out;
+  if (hh >= 0 && hh < 24 && mm >= 0 && mm < 60 && ss >= 0 && ss < 60) {
+    hour = hh;
+    minute = mm;
+    seconds = ss;
   }
+
+out:
+
+  if (tokens)
+    g_strfreev (tokens);
+
+  if (year > 0)
+    dt = gst_date_time_new (0.0, year, month, day, hour, minute, seconds);
+
+  return dt;
 }
 
 static gboolean
@@ -302,7 +344,7 @@ gst_flv_demux_parse_metadata_item (GstFlvDemux * demux, GstByteReader * reader,
   GST_DEBUG_OBJECT (demux, "tag name %s, tag type %d", tag_name, tag_type);
 
   switch (tag_type) {
-    case 0:                    // Double
+    case 0:                    /* Double */
     {                           /* Use a union to read the uint64 and then as a double */
       gdouble d = 0;
 
@@ -328,13 +370,19 @@ gst_flv_demux_parse_metadata_item (GstFlvDemux * demux, GstByteReader * reader,
         demux->h = d;
       } else if (!strcmp (tag_name, "framerate")) {
         demux->framerate = d;
+      } else if (!strcmp (tag_name, "audiodatarate")) {
+        gst_tag_list_add (demux->audio_tags, GST_TAG_MERGE_REPLACE,
+            GST_TAG_NOMINAL_BITRATE, (guint) (d * 1024), NULL);
+      } else if (!strcmp (tag_name, "videodatarate")) {
+        gst_tag_list_add (demux->video_tags, GST_TAG_MERGE_REPLACE,
+            GST_TAG_NOMINAL_BITRATE, (guint) (d * 1024), NULL);
       } else {
         GST_INFO_OBJECT (demux, "Tag \'%s\' not handled", tag_name);
       }
 
       break;
     }
-    case 1:                    // Boolean
+    case 1:                    /* Boolean */
     {
       guint8 b = 0;
 
@@ -347,7 +395,7 @@ gst_flv_demux_parse_metadata_item (GstFlvDemux * demux, GstByteReader * reader,
 
       break;
     }
-    case 2:                    // String
+    case 2:                    /* String */
     {
       gchar *s = NULL;
 
@@ -358,23 +406,24 @@ gst_flv_demux_parse_metadata_item (GstFlvDemux * demux, GstByteReader * reader,
       GST_DEBUG_OBJECT (demux, "%s => (string) %s", tag_name, s);
 
       if (!strcmp (tag_name, "creationdate")) {
-        GDate *date = g_date_new ();
+        GstDateTime *dt;
 
-        parse_flv_demux_parse_date_string (date, s);
-        if (!g_date_valid (date)) {
-          GST_DEBUG_OBJECT (demux, "Failed to parse string as date");
+        dt = parse_flv_demux_parse_date_string (s);
+        if (dt == NULL) {
+          GST_DEBUG_OBJECT (demux, "Failed to parse '%s' into datetime", s);
         } else {
           gst_tag_list_add (demux->taglist, GST_TAG_MERGE_REPLACE,
-              GST_TAG_DATE, date, NULL);
+              GST_TAG_DATE_TIME, dt, NULL);
+          gst_date_time_unref (dt);
         }
-        g_date_free (date);
       } else if (!strcmp (tag_name, "creator")) {
         gst_tag_list_add (demux->taglist, GST_TAG_MERGE_REPLACE,
             GST_TAG_ARTIST, s, NULL);
       } else if (!strcmp (tag_name, "title")) {
         gst_tag_list_add (demux->taglist, GST_TAG_MERGE_REPLACE,
             GST_TAG_TITLE, s, NULL);
-      } else if (!strcmp (tag_name, "metadatacreator")) {
+      } else if (!strcmp (tag_name, "metadatacreator")
+          || !strcmp (tag_name, "encoder")) {
         gst_tag_list_add (demux->taglist, GST_TAG_MERGE_REPLACE,
             GST_TAG_ENCODER, s, NULL);
       } else {
@@ -385,7 +434,7 @@ gst_flv_demux_parse_metadata_item (GstFlvDemux * demux, GstByteReader * reader,
 
       break;
     }
-    case 3:                    // Object
+    case 3:                    /* Object */
     {
       gboolean end_of_object_marker = FALSE;
 
@@ -401,7 +450,7 @@ gst_flv_demux_parse_metadata_item (GstFlvDemux * demux, GstByteReader * reader,
 
       break;
     }
-    case 8:                    // ECMA array
+    case 8:                    /* ECMA array */
     {
       guint32 nb_elems = 0;
       gboolean end_of_object_marker = FALSE;
@@ -424,7 +473,7 @@ gst_flv_demux_parse_metadata_item (GstFlvDemux * demux, GstByteReader * reader,
 
       break;
     }
-    case 9:                    // End marker
+    case 9:                    /* End marker */
     {
       GST_DEBUG_OBJECT (demux, "end marker ?");
       if (tag_name[0] == '\0') {
@@ -436,7 +485,7 @@ gst_flv_demux_parse_metadata_item (GstFlvDemux * demux, GstByteReader * reader,
 
       break;
     }
-    case 10:                   // Array
+    case 10:                   /* Array */
     {
       guint32 nb_elems = 0;
 
@@ -489,7 +538,7 @@ gst_flv_demux_parse_metadata_item (GstFlvDemux * demux, GstByteReader * reader,
 
       break;
     }
-    case 11:                   // Date
+    case 11:                   /* Date */
     {
       gdouble d = 0;
       gint16 i = 0;
@@ -521,6 +570,28 @@ error:
   return FALSE;
 }
 
+static void
+gst_flv_demux_clear_tags (GstFlvDemux * demux)
+{
+  GST_DEBUG_OBJECT (demux, "clearing taglist");
+
+  if (demux->taglist) {
+    gst_tag_list_unref (demux->taglist);
+  }
+  demux->taglist = gst_tag_list_new_empty ();
+  gst_tag_list_set_scope (demux->taglist, GST_TAG_SCOPE_GLOBAL);
+
+  if (demux->audio_tags) {
+    gst_tag_list_unref (demux->audio_tags);
+  }
+  demux->audio_tags = gst_tag_list_new_empty ();
+
+  if (demux->video_tags) {
+    gst_tag_list_unref (demux->video_tags);
+  }
+  demux->video_tags = gst_tag_list_new_empty ();
+}
+
 static GstFlowReturn
 gst_flv_demux_parse_tag_script (GstFlvDemux * demux, GstBuffer * buffer)
 {
@@ -534,7 +605,7 @@ gst_flv_demux_parse_tag_script (GstFlvDemux * demux, GstBuffer * buffer)
   gst_buffer_map (buffer, &map, GST_MAP_READ);
   gst_byte_reader_init (&reader, map.data, map.size);
 
-  gst_byte_reader_skip (&reader, 7);
+  gst_byte_reader_skip_unchecked (&reader, 7);
 
   GST_LOG_OBJECT (demux, "parsing a script tag");
 
@@ -554,6 +625,8 @@ gst_flv_demux_parse_tag_script (GstFlvDemux * demux, GstBuffer * buffer)
       gboolean end_marker = FALSE;
       GST_DEBUG_OBJECT (demux, "we have a metadata script object");
 
+      gst_flv_demux_clear_tags (demux);
+
       if (!gst_byte_reader_get_uint8 (&reader, &type)) {
         g_free (function_name);
         goto cleanup;
@@ -596,12 +669,12 @@ gst_flv_demux_parse_tag_script (GstFlvDemux * demux, GstBuffer * buffer)
           goto cleanup;
       }
 
-      demux->push_tags = TRUE;
+      gst_flv_demux_push_tags (demux);
     }
 
     g_free (function_name);
 
-    if (demux->index && demux->times && demux->filepositions) {
+    if (demux->times && demux->filepositions) {
       guint num;
 
       /* If an index was found, insert associations */
@@ -625,13 +698,35 @@ cleanup:
 }
 
 static gboolean
+have_group_id (GstFlvDemux * demux)
+{
+  GstEvent *event;
+
+  event = gst_pad_get_sticky_event (demux->sinkpad, GST_EVENT_STREAM_START, 0);
+  if (event) {
+    if (gst_event_parse_group_id (event, &demux->group_id))
+      demux->have_group_id = TRUE;
+    else
+      demux->have_group_id = FALSE;
+    gst_event_unref (event);
+  } else if (!demux->have_group_id) {
+    demux->have_group_id = TRUE;
+    demux->group_id = gst_util_group_id_next ();
+  }
+
+  return demux->have_group_id;
+}
+
+static gboolean
 gst_flv_demux_audio_negotiate (GstFlvDemux * demux, guint32 codec_tag,
     guint32 rate, guint32 channels, guint32 width)
 {
-  GstCaps *caps = NULL;
-  gchar *codec_name = NULL;
+  GstCaps *caps = NULL, *old_caps;
   gboolean ret = FALSE;
   guint adjusted_rate = rate;
+  guint adjusted_channels = channels;
+  GstEvent *event;
+  gchar *stream_id;
 
   switch (codec_tag) {
     case 1:
@@ -667,29 +762,42 @@ gst_flv_demux_audio_negotiate (GstFlvDemux * demux, guint32 codec_tag,
       break;
     case 10:
     {
-      if (demux->audio_codec_data) {
-        GstMapInfo map;
+      GstMapInfo map;
+      if (!demux->audio_codec_data) {
+        GST_DEBUG_OBJECT (demux, "don't have AAC codec data yet");
+        ret = TRUE;
+        goto done;
+      }
 
-        gst_buffer_map (demux->audio_codec_data, &map, GST_MAP_READ);
+      gst_buffer_map (demux->audio_codec_data, &map, GST_MAP_READ);
 
-        /* use codec-data to extract and verify samplerate */
-        if (map.size >= 2) {
-          gint freq_index;
+      /* use codec-data to extract and verify samplerate */
+      if (map.size >= 2) {
+        gint freq_index;
 
-          freq_index = GST_READ_UINT16_BE (map.data);
-          freq_index = (freq_index & 0x0780) >> 7;
-          adjusted_rate =
-              gst_codec_utils_aac_get_sample_rate_from_index (freq_index);
+        freq_index = GST_READ_UINT16_BE (map.data);
+        freq_index = (freq_index & 0x0780) >> 7;
+        adjusted_rate =
+            gst_codec_utils_aac_get_sample_rate_from_index (freq_index);
 
-          if (adjusted_rate && (rate != adjusted_rate)) {
-            GST_LOG_OBJECT (demux, "Ajusting AAC sample rate %d -> %d", rate,
-                adjusted_rate);
-          } else {
-            adjusted_rate = rate;
-          }
+        if (adjusted_rate && (rate != adjusted_rate)) {
+          GST_LOG_OBJECT (demux, "Ajusting AAC sample rate %d -> %d", rate,
+              adjusted_rate);
+        } else {
+          adjusted_rate = rate;
+        }
+
+        adjusted_channels =
+            gst_codec_utils_aac_get_channels (map.data, map.size);
+
+        if (adjusted_channels && (channels != adjusted_channels)) {
+          GST_LOG_OBJECT (demux, "Ajusting AAC channels %d -> %d", channels,
+              adjusted_channels);
+        } else {
+          adjusted_channels = channels;
         }
-        gst_buffer_unmap (demux->audio_codec_data, &map);
       }
+      gst_buffer_unmap (demux->audio_codec_data, &map);
 
       caps = gst_caps_new_simple ("audio/mpeg",
           "mpegversion", G_TYPE_INT, 4, "framed", G_TYPE_BOOLEAN, TRUE,
@@ -703,10 +811,65 @@ gst_flv_demux_audio_negotiate (GstFlvDemux * demux, guint32 codec_tag,
       caps = gst_caps_new_empty_simple ("audio/x-mulaw");
       break;
     case 11:
+    {
+      GValue streamheader = G_VALUE_INIT;
+      GValue value = G_VALUE_INIT;
+      GstByteWriter w;
+      GstStructure *structure;
+      GstBuffer *buf;
+      GstTagList *tags;
+
       caps = gst_caps_new_empty_simple ("audio/x-speex");
+      structure = gst_caps_get_structure (caps, 0);
+
+      GST_DEBUG_OBJECT (demux, "generating speex header");
+
+      /* Speex decoder expects streamheader to be { [header], [comment] } */
+      g_value_init (&streamheader, GST_TYPE_ARRAY);
+
+      /* header part */
+      gst_byte_writer_init_with_size (&w, 80, TRUE);
+      gst_byte_writer_put_data (&w, (guint8 *) "Speex   ", 8);
+      gst_byte_writer_put_data (&w, (guint8 *) "1.1.12", 7);
+      gst_byte_writer_fill (&w, 0, 13);
+      gst_byte_writer_put_uint32_le (&w, 1);    /* version */
+      gst_byte_writer_put_uint32_le (&w, 80);   /* header_size */
+      gst_byte_writer_put_uint32_le (&w, 16000);        /* rate */
+      gst_byte_writer_put_uint32_le (&w, 1);    /* mode: Wideband */
+      gst_byte_writer_put_uint32_le (&w, 4);    /* mode_bitstream_version */
+      gst_byte_writer_put_uint32_le (&w, 1);    /* nb_channels: 1 */
+      gst_byte_writer_put_uint32_le (&w, -1);   /* bitrate */
+      gst_byte_writer_put_uint32_le (&w, 0x50); /* frame_size */
+      gst_byte_writer_put_uint32_le (&w, 0);    /* VBR */
+      gst_byte_writer_put_uint32_le (&w, 1);    /* frames_per_packet */
+      gst_byte_writer_put_uint32_le (&w, 0);    /* extra_headers */
+      gst_byte_writer_put_uint32_le (&w, 0);    /* reserved1 */
+      gst_byte_writer_put_uint32_le (&w, 0);    /* reserved2 */
+      g_assert (gst_byte_writer_get_size (&w) == 80);
+
+      g_value_init (&value, GST_TYPE_BUFFER);
+      g_value_take_boxed (&value, gst_byte_writer_reset_and_get_buffer (&w));
+      gst_value_array_append_value (&streamheader, &value);
+      g_value_unset (&value);
+
+      /* comment part */
+      g_value_init (&value, GST_TYPE_BUFFER);
+      tags = gst_tag_list_new_empty ();
+      buf = gst_tag_list_to_vorbiscomment_buffer (tags, NULL, 0, "No comments");
+      gst_tag_list_unref (tags);
+      g_value_take_boxed (&value, buf);
+      gst_value_array_append_value (&streamheader, &value);
+      g_value_unset (&value);
+
+      gst_structure_take_value (structure, "streamheader", &streamheader);
+
+      channels = 1;
+      adjusted_rate = 16000;
       break;
+    }
     default:
       GST_WARNING_OBJECT (demux, "unsupported audio codec tag %u", codec_tag);
+      break;
   }
 
   if (G_UNLIKELY (!caps)) {
@@ -715,15 +878,34 @@ gst_flv_demux_audio_negotiate (GstFlvDemux * demux, guint32 codec_tag,
   }
 
   gst_caps_set_simple (caps, "rate", G_TYPE_INT, adjusted_rate,
-      "channels", G_TYPE_INT, channels, NULL);
+      "channels", G_TYPE_INT, adjusted_channels, NULL);
 
   if (demux->audio_codec_data) {
     gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER,
         demux->audio_codec_data, NULL);
   }
 
-  ret = gst_pad_set_caps (demux->audio_pad, caps);
+  old_caps = gst_pad_get_current_caps (demux->audio_pad);
+  if (!old_caps) {
+    stream_id =
+        gst_pad_create_stream_id (demux->audio_pad, GST_ELEMENT_CAST (demux),
+        "audio");
 
+    event = gst_event_new_stream_start (stream_id);
+    if (have_group_id (demux))
+      gst_event_set_group_id (event, demux->group_id);
+    gst_pad_push_event (demux->audio_pad, event);
+    g_free (stream_id);
+  }
+  if (!old_caps || !gst_caps_is_equal (old_caps, caps))
+    ret = gst_pad_set_caps (demux->audio_pad, caps);
+  else
+    ret = TRUE;
+
+  if (old_caps)
+    gst_caps_unref (old_caps);
+
+done:
   if (G_LIKELY (ret)) {
     /* Store the caps we got from tags */
     demux->audio_codec_tag = codec_tag;
@@ -731,24 +913,21 @@ gst_flv_demux_audio_negotiate (GstFlvDemux * demux, guint32 codec_tag,
     demux->channels = channels;
     demux->width = width;
 
-    codec_name = gst_pb_utils_get_codec_description (caps);
+    if (caps) {
+      GST_DEBUG_OBJECT (demux->audio_pad, "successfully negotiated caps %"
+          GST_PTR_FORMAT, caps);
 
-    if (codec_name) {
-      if (demux->taglist == NULL)
-        demux->taglist = gst_tag_list_new_empty ();
-      gst_tag_list_add (demux->taglist, GST_TAG_MERGE_REPLACE,
-          GST_TAG_AUDIO_CODEC, codec_name, NULL);
-      g_free (codec_name);
+      gst_flv_demux_push_tags (demux);
+    } else {
+      GST_DEBUG_OBJECT (demux->audio_pad, "delayed setting caps");
     }
-
-    GST_DEBUG_OBJECT (demux->audio_pad, "successfully negotiated caps %"
-        GST_PTR_FORMAT, caps);
   } else {
     GST_WARNING_OBJECT (demux->audio_pad, "failed negotiating caps %"
         GST_PTR_FORMAT, caps);
   }
 
-  gst_caps_unref (caps);
+  if (caps)
+    gst_caps_unref (caps);
 
 beach:
   return ret;
@@ -771,42 +950,81 @@ gst_flv_demux_push_src_event (GstFlvDemux * demux, GstEvent * event)
 }
 
 static void
-gst_flv_demux_push_tags (GstFlvDemux * demux)
+gst_flv_demux_add_codec_tag (GstFlvDemux * demux, const gchar * tag,
+    GstPad * pad)
 {
-  if (demux->has_audio && !demux->audio_pad) {
-    GST_DEBUG_OBJECT (demux,
-        "Waiting for audio stream pad to come up before we can push tags");
-    return;
+  if (pad) {
+    GstCaps *caps = gst_pad_get_current_caps (pad);
+
+    if (caps) {
+      gchar *codec_name = gst_pb_utils_get_codec_description (caps);
+
+      if (codec_name) {
+        gst_tag_list_add (demux->taglist, GST_TAG_MERGE_REPLACE,
+            tag, codec_name, NULL);
+        g_free (codec_name);
+      }
+
+      gst_caps_unref (caps);
+    }
   }
-  if (demux->has_video && !demux->video_pad) {
-    GST_DEBUG_OBJECT (demux,
-        "Waiting for video stream pad to come up before we can push tags");
-    return;
+}
+
+static void
+gst_flv_demux_push_tags (GstFlvDemux * demux)
+{
+  gst_flv_demux_add_codec_tag (demux, GST_TAG_AUDIO_CODEC, demux->audio_pad);
+  gst_flv_demux_add_codec_tag (demux, GST_TAG_VIDEO_CODEC, demux->video_pad);
+
+  GST_DEBUG_OBJECT (demux, "pushing %" GST_PTR_FORMAT, demux->taglist);
+
+  gst_flv_demux_push_src_event (demux,
+      gst_event_new_tag (gst_tag_list_copy (demux->taglist)));
+
+#ifdef TIZEN_FEATURE_FLVDEMUX_MODIFICATION
+  GST_DEBUG_OBJECT (demux, "post tag msg %" GST_PTR_FORMAT, demux->taglist);
+
+  /* post message flv tag (for early recive application) */
+  gst_element_post_message (GST_ELEMENT_CAST (demux),
+      gst_message_new_tag (GST_OBJECT_CAST (demux),
+        gst_tag_list_copy (demux->taglist)));
+#endif
+
+  if (demux->audio_pad) {
+    GST_DEBUG_OBJECT (demux->audio_pad, "pushing audio %" GST_PTR_FORMAT,
+        demux->audio_tags);
+    gst_pad_push_event (demux->audio_pad,
+        gst_event_new_tag (gst_tag_list_copy (demux->audio_tags)));
   }
-  if (demux->taglist) {
-    GST_DEBUG_OBJECT (demux, "pushing tags out %" GST_PTR_FORMAT,
-        demux->taglist);
-    gst_flv_demux_push_src_event (demux, gst_event_new_tag (demux->taglist));
-    demux->taglist = gst_tag_list_new_empty ();
-    demux->push_tags = FALSE;
+
+  if (demux->video_pad) {
+    GST_DEBUG_OBJECT (demux->video_pad, "pushing video %" GST_PTR_FORMAT,
+        demux->video_tags);
+    gst_pad_push_event (demux->video_pad,
+        gst_event_new_tag (gst_tag_list_copy (demux->video_tags)));
   }
 }
 
-static void
-gst_flv_demux_update_resync (GstFlvDemux * demux, guint32 pts, gboolean discont,
+static gboolean
+gst_flv_demux_update_resync (GstFlvDemux * demux, guint32 dts, gboolean discont,
     guint32 * last, GstClockTime * offset)
 {
-  gint32 dpts = pts - *last;
-  if (!discont && ABS (dpts) >= RESYNC_THRESHOLD) {
+  gboolean ret = FALSE;
+  gint32 ddts = dts - *last;
+  if (!discont && ddts <= -RESYNC_THRESHOLD) {
     /* Theoretically, we should use substract the duration of the last buffer,
        but this demuxer sends no durations on buffers, not sure if it cannot
        know, or just does not care to calculate. */
-    *offset -= dpts * GST_MSECOND;
+    *offset -= ddts * GST_MSECOND;
     GST_WARNING_OBJECT (demux,
-        "Large pts gap (%" G_GINT32_FORMAT " ms), assuming resync, offset now %"
-        GST_TIME_FORMAT "", dpts, GST_TIME_ARGS (*offset));
+        "Large dts gap (%" G_GINT32_FORMAT " ms), assuming resync, offset now %"
+        GST_TIME_FORMAT "", ddts, GST_TIME_ARGS (*offset));
+
+    ret = TRUE;
   }
-  *last = pts;
+  *last = dts;
+
+  return ret;
 }
 
 static GstFlowReturn
@@ -822,9 +1040,14 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer)
 
   GST_LOG_OBJECT (demux, "parsing an audio tag");
 
-  if (demux->no_more_pads && !demux->audio_pad) {
-    GST_WARNING_OBJECT (demux,
-        "Signaled no-more-pads already but had no audio pad -- ignoring");
+  if (G_UNLIKELY (!demux->audio_pad && demux->no_more_pads)) {
+#ifndef GST_DISABLE_DEBUG
+    if (G_UNLIKELY (!demux->no_audio_warned)) {
+      GST_WARNING_OBJECT (demux,
+          "Signaled no-more-pads already but had no audio pad -- ignoring");
+      demux->no_audio_warned = TRUE;
+    }
+#endif
     return GST_FLOW_OK;
   }
 
@@ -883,18 +1106,53 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer)
   }
 
   /* codec tags with special rates */
-  if (codec_tag == 5 || codec_tag == 14)
+  if (codec_tag == 5 || codec_tag == 14 || codec_tag == 7 || codec_tag == 8)
     rate = 8000;
-  else if (codec_tag == 4)
+  else if ((codec_tag == 4) || (codec_tag == 11))
     rate = 16000;
 
   GST_LOG_OBJECT (demux, "audio tag with %d channels, %dHz sampling rate, "
       "%d bits width, codec tag %u (flags %02X)", channels, rate, width,
       codec_tag, flags);
 
+  if (codec_tag == 10) {
+    guint8 aac_packet_type = GST_READ_UINT8 (data + 8);
+
+    switch (aac_packet_type) {
+      case 0:
+      {
+        /* AudioSpecificConfig data */
+        GST_LOG_OBJECT (demux, "got an AAC codec data packet");
+        if (demux->audio_codec_data) {
+          gst_buffer_unref (demux->audio_codec_data);
+        }
+        demux->audio_codec_data =
+            gst_buffer_copy_region (buffer, GST_BUFFER_COPY_MEMORY,
+            7 + codec_data, demux->tag_data_size - codec_data);
+
+        /* Use that buffer data in the caps */
+        if (demux->audio_pad)
+          gst_flv_demux_audio_negotiate (demux, codec_tag, rate, channels,
+              width);
+        goto beach;
+      }
+      case 1:
+        if (!demux->audio_codec_data) {
+          GST_ERROR_OBJECT (demux, "got AAC audio packet before codec data");
+          ret = GST_FLOW_OK;
+          goto beach;
+        }
+        /* AAC raw packet */
+        GST_LOG_OBJECT (demux, "got a raw AAC audio packet");
+        break;
+      default:
+        GST_WARNING_OBJECT (demux, "invalid AAC packet type %u",
+            aac_packet_type);
+    }
+  }
+
   /* If we don't have our audio pad created, then create it. */
   if (G_UNLIKELY (!demux->audio_pad)) {
-
     demux->audio_pad =
         gst_pad_new_from_template (gst_element_class_get_pad_template
         (GST_ELEMENT_GET_CLASS (demux), "audio"), "audio");
@@ -938,6 +1196,7 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer)
     /* We need to set caps before adding */
     gst_element_add_pad (GST_ELEMENT (demux),
         gst_object_ref (demux->audio_pad));
+    gst_flow_combiner_add_pad (demux->flowcombiner, demux->audio_pad);
 
     /* We only emit no more pads when we have audio and video. Indeed we can
      * not trust the FLV header to tell us if there will be only audio or
@@ -946,7 +1205,6 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer)
       GST_DEBUG_OBJECT (demux, "emitting no more pads");
       gst_element_no_more_pads (GST_ELEMENT (demux));
       demux->no_more_pads = TRUE;
-      demux->push_tags = TRUE;
     }
   }
 
@@ -955,6 +1213,8 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer)
           codec_tag != demux->audio_codec_tag || width != demux->width)) {
     GST_DEBUG_OBJECT (demux, "audio settings have changed, changing caps");
 
+    gst_buffer_replace (&demux->audio_codec_data, NULL);
+
     /* Negotiate caps */
     if (!gst_flv_demux_audio_negotiate (demux, codec_tag, rate, channels,
             width)) {
@@ -963,10 +1223,6 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer)
     }
   }
 
-  /* Push taglist if present */
-  if (G_UNLIKELY (demux->push_tags))
-    gst_flv_demux_push_tags (demux);
-
   /* Check if we have anything to push */
   if (demux->tag_data_size <= codec_data) {
     GST_LOG_OBJECT (demux, "Nothing left in this tag, returning");
@@ -977,39 +1233,15 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer)
   outbuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_MEMORY,
       7 + codec_data, demux->tag_data_size - codec_data);
 
-  if (demux->audio_codec_tag == 10) {
-    guint8 aac_packet_type = GST_READ_UINT8 (data + 8);
-
-    switch (aac_packet_type) {
-      case 0:
-      {
-        /* AudioSpecificConfig data */
-        GST_LOG_OBJECT (demux, "got an AAC codec data packet");
-        if (demux->audio_codec_data) {
-          gst_buffer_unref (demux->audio_codec_data);
-        }
-        demux->audio_codec_data = outbuf;
-        /* Use that buffer data in the caps */
-        gst_flv_demux_audio_negotiate (demux, codec_tag, rate, channels, width);
-        goto beach;
-        break;
-      }
-      case 1:
-        /* AAC raw packet */
-        GST_LOG_OBJECT (demux, "got a raw AAC audio packet");
-        break;
-      default:
-        GST_WARNING_OBJECT (demux, "invalid AAC packet type %u",
-            aac_packet_type);
-    }
-  }
-
   /* detect (and deem to be resyncs)  large pts gaps */
-  gst_flv_demux_update_resync (demux, pts, demux->audio_need_discont,
-      &demux->last_audio_pts, &demux->audio_time_offset);
+  if (gst_flv_demux_update_resync (demux, pts, demux->audio_need_discont,
+          &demux->last_audio_pts, &demux->audio_time_offset)) {
+    GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_RESYNC);
+  }
 
   /* Fill buffer with data */
-  GST_BUFFER_TIMESTAMP (outbuf) = pts * GST_MSECOND + demux->audio_time_offset;
+  GST_BUFFER_PTS (outbuf) = pts * GST_MSECOND + demux->audio_time_offset;
+  GST_BUFFER_DTS (outbuf) = GST_BUFFER_PTS (outbuf);
   GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE;
   GST_BUFFER_OFFSET (outbuf) = demux->audio_offset++;
   GST_BUFFER_OFFSET_END (outbuf) = demux->audio_offset;
@@ -1020,7 +1252,7 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer)
 
   /* Only add audio frames to the index if we have no video,
    * and if the index is not yet complete */
-  if (!demux->has_video && demux->index && !demux->indexed) {
+  if (!demux->has_video && !demux->indexed) {
     gst_flv_demux_parse_and_add_index_entry (demux,
         GST_BUFFER_TIMESTAMP (outbuf), demux->cur_tag_offset, TRUE);
   }
@@ -1066,38 +1298,31 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer)
 
   if (G_UNLIKELY (!demux->no_more_pads
           && (GST_CLOCK_DIFF (demux->audio_start,
-                  GST_BUFFER_TIMESTAMP (outbuf)) > 6 * GST_SECOND))) {
+                  GST_BUFFER_TIMESTAMP (outbuf)) > NO_MORE_PADS_THRESHOLD))) {
     GST_DEBUG_OBJECT (demux,
         "Signalling no-more-pads because no video stream was found"
         " after 6 seconds of audio");
     gst_element_no_more_pads (GST_ELEMENT_CAST (demux));
     demux->no_more_pads = TRUE;
-    demux->push_tags = TRUE;
   }
 
   /* Push downstream */
   ret = gst_pad_push (demux->audio_pad, outbuf);
-  if (G_UNLIKELY (ret != GST_FLOW_OK)) {
-    if (demux->segment.rate < 0.0 && ret == GST_FLOW_EOS &&
-        demux->segment.position > demux->segment.stop) {
-      /* In reverse playback we can get a GST_FLOW_EOS when
-       * we are at the end of the segment, so we just need to jump
-       * back to the previous section. */
-      GST_DEBUG_OBJECT (demux, "downstream has reached end of segment");
-      demux->audio_done = TRUE;
-      ret = GST_FLOW_OK;
-    } else {
-      GST_WARNING_OBJECT (demux, "failed pushing a %" G_GUINT64_FORMAT
-          " bytes audio buffer: %s", demux->tag_data_size,
-          gst_flow_get_name (ret));
-      if (ret == GST_FLOW_NOT_LINKED) {
-        demux->audio_linked = FALSE;
-      }
-      goto beach;
-    }
+
+  if (G_UNLIKELY (ret != GST_FLOW_OK) &&
+      demux->segment.rate < 0.0 && ret == GST_FLOW_EOS &&
+      demux->segment.position > demux->segment.stop) {
+    /* In reverse playback we can get a GST_FLOW_EOS when
+     * we are at the end of the segment, so we just need to jump
+     * back to the previous section. */
+    GST_DEBUG_OBJECT (demux, "downstream has reached end of segment");
+    demux->audio_done = TRUE;
+    ret = GST_FLOW_OK;
+    goto beach;
   }
 
-  demux->audio_linked = TRUE;
+  ret = gst_flow_combiner_update_pad_flow (demux->flowcombiner,
+      demux->audio_pad, ret);
 
 beach:
   gst_buffer_unmap (buffer, &map);
@@ -1109,13 +1334,16 @@ static gboolean
 gst_flv_demux_video_negotiate (GstFlvDemux * demux, guint32 codec_tag)
 {
   gboolean ret = FALSE;
-  GstCaps *caps = NULL;
-  gchar *codec_name = NULL;
+  GstCaps *caps = NULL, *old_caps;
+  GstEvent *event;
+  gchar *stream_id;
 
   /* Generate caps for that pad */
   switch (codec_tag) {
     case 2:
-      caps = gst_caps_new_empty_simple ("video/x-flash-video");
+      caps =
+          gst_caps_new_simple ("video/x-flash-video", "flvversion", G_TYPE_INT,
+          1, NULL);
       break;
     case 3:
       caps = gst_caps_new_empty_simple ("video/x-flash-screen");
@@ -1127,10 +1355,27 @@ gst_flv_demux_video_negotiate (GstFlvDemux * demux, guint32 codec_tag)
       caps = gst_caps_new_empty_simple ("video/x-vp6-alpha");
       break;
     case 7:
+      if (!demux->video_codec_data) {
+        GST_DEBUG_OBJECT (demux, "don't have h264 codec data yet");
+        ret = TRUE;
+        goto done;
+      }
       caps =
           gst_caps_new_simple ("video/x-h264", "stream-format", G_TYPE_STRING,
           "avc", NULL);
       break;
+      /* The following two are non-standard but apparently used, see in ffmpeg
+       * https://git.videolan.org/?p=ffmpeg.git;a=blob;f=libavformat/flvdec.c;h=2bf1e059e1cbeeb79e4af9542da23f4560e1cf59;hb=b18d6c58000beed872d6bb1fe7d0fbe75ae26aef#l254
+       * https://git.videolan.org/?p=ffmpeg.git;a=blob;f=libavformat/flvdec.c;h=2bf1e059e1cbeeb79e4af9542da23f4560e1cf59;hb=b18d6c58000beed872d6bb1fe7d0fbe75ae26aef#l282
+       */
+    case 8:
+      caps = gst_caps_new_empty_simple ("video/x-h263");
+      break;
+    case 9:
+      caps =
+          gst_caps_new_simple ("video/mpeg", "mpegversion", G_TYPE_INT, 4,
+          "systemstream", G_TYPE_BOOLEAN, FALSE, NULL);
+      break;
     default:
       GST_WARNING_OBJECT (demux, "unsupported video codec tag %u", codec_tag);
   }
@@ -1140,8 +1385,10 @@ gst_flv_demux_video_negotiate (GstFlvDemux * demux, guint32 codec_tag)
     goto beach;
   }
 
-  gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
-      demux->par_x, demux->par_y, NULL);
+  if (demux->got_par) {
+    gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
+        demux->par_x, demux->par_y, NULL);
+  }
 
   if (G_LIKELY (demux->w)) {
     gst_caps_set_simple (caps, "width", G_TYPE_INT, demux->w, NULL);
@@ -1154,7 +1401,7 @@ gst_flv_demux_video_negotiate (GstFlvDemux * demux, guint32 codec_tag)
   if (G_LIKELY (demux->framerate)) {
     gint num = 0, den = 0;
 
-    gst_util_double_to_fraction (demux->framerate, &num, &den);
+    gst_video_guess_framerate (GST_SECOND / demux->framerate, &num, &den);
     GST_DEBUG_OBJECT (demux->video_pad,
         "fps to be used on caps %f (as a fraction = %d/%d)", demux->framerate,
         num, den);
@@ -1167,30 +1414,47 @@ gst_flv_demux_video_negotiate (GstFlvDemux * demux, guint32 codec_tag)
         demux->video_codec_data, NULL);
   }
 
-  ret = gst_pad_set_caps (demux->video_pad, caps);
+  old_caps = gst_pad_get_current_caps (demux->video_pad);
+  if (!old_caps) {
+    stream_id =
+        gst_pad_create_stream_id (demux->video_pad, GST_ELEMENT_CAST (demux),
+        "video");
+    event = gst_event_new_stream_start (stream_id);
+    g_free (stream_id);
+
+    if (have_group_id (demux))
+      gst_event_set_group_id (event, demux->group_id);
+    gst_pad_push_event (demux->video_pad, event);
+  }
+
+  if (!old_caps || !gst_caps_is_equal (old_caps, caps))
+    ret = gst_pad_set_caps (demux->video_pad, caps);
+  else
+    ret = TRUE;
+
+  if (old_caps)
+    gst_caps_unref (old_caps);
 
+done:
   if (G_LIKELY (ret)) {
     /* Store the caps we have set */
     demux->video_codec_tag = codec_tag;
 
-    codec_name = gst_pb_utils_get_codec_description (caps);
+    if (caps) {
+      GST_DEBUG_OBJECT (demux->video_pad, "successfully negotiated caps %"
+          GST_PTR_FORMAT, caps);
 
-    if (codec_name) {
-      if (demux->taglist == NULL)
-        demux->taglist = gst_tag_list_new_empty ();
-      gst_tag_list_add (demux->taglist, GST_TAG_MERGE_REPLACE,
-          GST_TAG_VIDEO_CODEC, codec_name, NULL);
-      g_free (codec_name);
+      gst_flv_demux_push_tags (demux);
+    } else {
+      GST_DEBUG_OBJECT (demux->video_pad, "delayed setting caps");
     }
-
-    GST_DEBUG_OBJECT (demux->video_pad, "successfully negotiated caps %"
-        GST_PTR_FORMAT, caps);
   } else {
     GST_WARNING_OBJECT (demux->video_pad, "failed negotiating caps %"
         GST_PTR_FORMAT, caps);
   }
 
-  gst_caps_unref (caps);
+  if (caps)
+    gst_caps_unref (caps);
 
 beach:
   return ret;
@@ -1200,7 +1464,8 @@ static GstFlowReturn
 gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
 {
   GstFlowReturn ret = GST_FLOW_OK;
-  guint32 pts = 0, codec_data = 1, pts_ext = 0;
+  guint32 dts = 0, codec_data = 1, dts_ext = 0;
+  gint32 cts = 0;
   gboolean keyframe = FALSE;
   guint8 flags = 0, codec_tag = 0;
   GstBuffer *outbuf;
@@ -1212,11 +1477,18 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
 
   GST_LOG_OBJECT (demux, "parsing a video tag");
 
-  if (demux->no_more_pads && !demux->video_pad) {
-    GST_WARNING_OBJECT (demux,
-        "Signaled no-more-pads already but had no audio pad -- ignoring");
+  if G_UNLIKELY
+    (!demux->video_pad && demux->no_more_pads) {
+#ifndef GST_DISABLE_DEBUG
+    if G_UNLIKELY
+      (!demux->no_video_warned) {
+      GST_WARNING_OBJECT (demux,
+          "Signaled no-more-pads already but had no video pad -- ignoring");
+      demux->no_video_warned = TRUE;
+      }
+#endif
     return GST_FLOW_OK;
-  }
+    }
 
   if (gst_buffer_get_size (buffer) < 12) {
     GST_ERROR_OBJECT (demux, "Too small tag size");
@@ -1227,14 +1499,14 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
   data = map.data;
 
   /* Grab information about video tag */
-  pts = GST_READ_UINT24_BE (data);
-  /* read the pts extension to 32 bits integer */
-  pts_ext = GST_READ_UINT8 (data + 3);
+  dts = GST_READ_UINT24_BE (data);
+  /* read the dts extension to 32 bits integer */
+  dts_ext = GST_READ_UINT8 (data + 3);
   /* Combine them */
-  pts |= pts_ext << 24;
+  dts |= dts_ext << 24;
 
-  GST_LOG_OBJECT (demux, "pts bytes %02X %02X %02X %02X (%d)", data[0], data[1],
-      data[2], data[3], pts);
+  GST_LOG_OBJECT (demux, "dts bytes %02X %02X %02X %02X (%d)", data[0], data[1],
+      data[2], data[3], dts);
 
   /* Skip the stream id and go directly to the flags */
   flags = GST_READ_UINT8 (data + 7);
@@ -1248,23 +1520,62 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
   if (codec_tag == 4 || codec_tag == 5) {
     codec_data = 2;
   } else if (codec_tag == 7) {
-    gint32 cts;
-
     codec_data = 5;
 
     cts = GST_READ_UINT24_BE (data + 9);
     cts = (cts + 0xff800000) ^ 0xff800000;
 
-    GST_LOG_OBJECT (demux, "got cts %d", cts);
+    if (cts < 0 && ABS (cts) > dts) {
+      GST_ERROR_OBJECT (demux, "Detected a negative composition time offset "
+          "'%d' that would lead to negative PTS, fixing", cts);
+      cts += ABS (cts) - dts;
+    }
 
-    /* avoid negative overflow */
-    if (cts >= 0 || pts >= -cts)
-      pts += cts;
+    GST_LOG_OBJECT (demux, "got cts %d", cts);
   }
 
   GST_LOG_OBJECT (demux, "video tag with codec tag %u, keyframe (%d) "
       "(flags %02X)", codec_tag, keyframe, flags);
 
+  if (codec_tag == 7) {
+    guint8 avc_packet_type = GST_READ_UINT8 (data + 8);
+
+    switch (avc_packet_type) {
+      case 0:
+      {
+        if (demux->tag_data_size < codec_data) {
+          GST_ERROR_OBJECT (demux, "Got invalid H.264 codec, ignoring.");
+          break;
+        }
+
+        /* AVCDecoderConfigurationRecord data */
+        GST_LOG_OBJECT (demux, "got an H.264 codec data packet");
+        if (demux->video_codec_data) {
+          gst_buffer_unref (demux->video_codec_data);
+        }
+        demux->video_codec_data = gst_buffer_copy_region (buffer,
+            GST_BUFFER_COPY_MEMORY, 7 + codec_data,
+            demux->tag_data_size - codec_data);;
+        /* Use that buffer data in the caps */
+        if (demux->video_pad)
+          gst_flv_demux_video_negotiate (demux, codec_tag);
+        goto beach;
+      }
+      case 1:
+        /* H.264 NALU packet */
+        if (!demux->video_codec_data) {
+          GST_ERROR_OBJECT (demux, "got H.264 video packet before codec data");
+          ret = GST_FLOW_OK;
+          goto beach;
+        }
+        GST_LOG_OBJECT (demux, "got a H.264 NALU video packet");
+        break;
+      default:
+        GST_WARNING_OBJECT (demux, "invalid video packet type %u",
+            avc_packet_type);
+    }
+  }
+
   /* If we don't have our video pad created, then create it. */
   if (G_UNLIKELY (!demux->video_pad)) {
     demux->video_pad =
@@ -1314,6 +1625,7 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
     /* We need to set caps before adding */
     gst_element_add_pad (GST_ELEMENT (demux),
         gst_object_ref (demux->video_pad));
+    gst_flow_combiner_add_pad (demux->flowcombiner, demux->video_pad);
 
     /* We only emit no more pads when we have audio and video. Indeed we can
      * not trust the FLV header to tell us if there will be only audio or
@@ -1322,14 +1634,13 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
       GST_DEBUG_OBJECT (demux, "emitting no more pads");
       gst_element_no_more_pads (GST_ELEMENT (demux));
       demux->no_more_pads = TRUE;
-      demux->push_tags = TRUE;
     }
   }
 
   /* Check if caps have changed */
   if (G_UNLIKELY (codec_tag != demux->video_codec_tag || demux->got_par)) {
-
     GST_DEBUG_OBJECT (demux, "video settings have changed, changing caps");
+    gst_buffer_replace (&demux->video_codec_data, NULL);
 
     if (!gst_flv_demux_video_negotiate (demux, codec_tag)) {
       ret = GST_FLOW_ERROR;
@@ -1341,10 +1652,6 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
     demux->got_par = FALSE;
   }
 
-  /* Push taglist if present */
-  if (G_UNLIKELY (demux->push_tags))
-    gst_flv_demux_push_tags (demux);
-
   /* Check if we have anything to push */
   if (demux->tag_data_size <= codec_data) {
     GST_LOG_OBJECT (demux, "Nothing left in this tag, returning");
@@ -1355,39 +1662,18 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
   outbuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_MEMORY,
       7 + codec_data, demux->tag_data_size - codec_data);
 
-  if (demux->video_codec_tag == 7) {
-    guint8 avc_packet_type = GST_READ_UINT8 (data + 8);
-
-    switch (avc_packet_type) {
-      case 0:
-      {
-        /* AVCDecoderConfigurationRecord data */
-        GST_LOG_OBJECT (demux, "got an H.264 codec data packet");
-        if (demux->video_codec_data) {
-          gst_buffer_unref (demux->video_codec_data);
-        }
-        demux->video_codec_data = outbuf;
-        /* Use that buffer data in the caps */
-        gst_flv_demux_video_negotiate (demux, codec_tag);
-        goto beach;
-        break;
-      }
-      case 1:
-        /* H.264 NALU packet */
-        GST_LOG_OBJECT (demux, "got a H.264 NALU video packet");
-        break;
-      default:
-        GST_WARNING_OBJECT (demux, "invalid video packet type %u",
-            avc_packet_type);
-    }
+  /* detect (and deem to be resyncs)  large dts gaps */
+  if (gst_flv_demux_update_resync (demux, dts, demux->video_need_discont,
+          &demux->last_video_dts, &demux->video_time_offset)) {
+    GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_RESYNC);
   }
 
-  /* detect (and deem to be resyncs)  large pts gaps */
-  gst_flv_demux_update_resync (demux, pts, demux->video_need_discont,
-      &demux->last_video_pts, &demux->video_time_offset);
-
   /* Fill buffer with data */
-  GST_BUFFER_TIMESTAMP (outbuf) = pts * GST_MSECOND + demux->video_time_offset;
+  GST_LOG_OBJECT (demux, "dts %u pts %u cts %d", dts, dts + cts, cts);
+
+  GST_BUFFER_PTS (outbuf) =
+      (dts + cts) * GST_MSECOND + demux->video_time_offset;
+  GST_BUFFER_DTS (outbuf) = dts * GST_MSECOND + demux->video_time_offset;
   GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE;
   GST_BUFFER_OFFSET (outbuf) = demux->video_offset++;
   GST_BUFFER_OFFSET_END (outbuf) = demux->video_offset;
@@ -1399,7 +1685,7 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
   if (!keyframe)
     GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT);
 
-  if (!demux->indexed && demux->index) {
+  if (!demux->indexed) {
     gst_flv_demux_parse_and_add_index_entry (demux,
         GST_BUFFER_TIMESTAMP (outbuf), demux->cur_tag_offset, keyframe);
   }
@@ -1430,10 +1716,10 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
   }
 
   GST_LOG_OBJECT (demux,
-      "pushing %" G_GSIZE_FORMAT " bytes buffer at pts %" GST_TIME_FORMAT
+      "pushing %" G_GSIZE_FORMAT " bytes buffer at dts %" GST_TIME_FORMAT
       " with duration %" GST_TIME_FORMAT ", offset %" G_GUINT64_FORMAT
       ", keyframe (%d)", gst_buffer_get_size (outbuf),
-      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)),
+      GST_TIME_ARGS (GST_BUFFER_DTS (outbuf)),
       GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)), GST_BUFFER_OFFSET (outbuf),
       keyframe);
 
@@ -1446,39 +1732,31 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
 
   if (G_UNLIKELY (!demux->no_more_pads
           && (GST_CLOCK_DIFF (demux->video_start,
-                  GST_BUFFER_TIMESTAMP (outbuf)) > 6 * GST_SECOND))) {
+                  GST_BUFFER_TIMESTAMP (outbuf)) > NO_MORE_PADS_THRESHOLD))) {
     GST_DEBUG_OBJECT (demux,
         "Signalling no-more-pads because no audio stream was found"
         " after 6 seconds of video");
     gst_element_no_more_pads (GST_ELEMENT_CAST (demux));
     demux->no_more_pads = TRUE;
-    demux->push_tags = TRUE;
   }
 
   /* Push downstream */
   ret = gst_pad_push (demux->video_pad, outbuf);
 
-  if (G_UNLIKELY (ret != GST_FLOW_OK)) {
-    if (demux->segment.rate < 0.0 && ret == GST_FLOW_EOS &&
-        demux->segment.position > demux->segment.stop) {
-      /* In reverse playback we can get a GST_FLOW_EOS when
-       * we are at the end of the segment, so we just need to jump
-       * back to the previous section. */
-      GST_DEBUG_OBJECT (demux, "downstream has reached end of segment");
-      demux->video_done = TRUE;
-      ret = GST_FLOW_OK;
-    } else {
-      GST_WARNING_OBJECT (demux, "failed pushing a %" G_GUINT64_FORMAT
-          " bytes video buffer: %s", demux->tag_data_size,
-          gst_flow_get_name (ret));
-      if (ret == GST_FLOW_NOT_LINKED) {
-        demux->video_linked = FALSE;
-      }
-      goto beach;
-    }
+  if (G_UNLIKELY (ret != GST_FLOW_OK) &&
+      demux->segment.rate < 0.0 && ret == GST_FLOW_EOS &&
+      demux->segment.position > demux->segment.stop) {
+    /* In reverse playback we can get a GST_FLOW_EOS when
+     * we are at the end of the segment, so we just need to jump
+     * back to the previous section. */
+    GST_DEBUG_OBJECT (demux, "downstream has reached end of segment");
+    demux->video_done = TRUE;
+    ret = GST_FLOW_OK;
+    goto beach;
   }
 
-  demux->video_linked = TRUE;
+  ret = gst_flow_combiner_update_pad_flow (demux->flowcombiner,
+      demux->video_pad, ret);
 
 beach:
   gst_buffer_unmap (buffer, &map);
@@ -1489,7 +1767,7 @@ static GstClockTime
 gst_flv_demux_parse_tag_timestamp (GstFlvDemux * demux, gboolean index,
     GstBuffer * buffer, size_t * tag_size)
 {
-  guint32 pts = 0, pts_ext = 0;
+  guint32 dts = 0, dts_ext = 0;
   guint32 tag_data_size;
   guint8 type;
   gboolean keyframe = TRUE;
@@ -1531,15 +1809,15 @@ gst_flv_demux_parse_tag_timestamp (GstFlvDemux * demux, gboolean index,
 
   data += 4;
 
-  GST_LOG_OBJECT (demux, "pts bytes %02X %02X %02X %02X", data[0], data[1],
+  GST_LOG_OBJECT (demux, "dts bytes %02X %02X %02X %02X", data[0], data[1],
       data[2], data[3]);
 
   /* Grab timestamp of tag tag */
-  pts = GST_READ_UINT24_BE (data);
-  /* read the pts extension to 32 bits integer */
-  pts_ext = GST_READ_UINT8 (data + 3);
+  dts = GST_READ_UINT24_BE (data);
+  /* read the dts extension to 32 bits integer */
+  dts_ext = GST_READ_UINT8 (data + 3);
   /* Combine them */
-  pts |= pts_ext << 24;
+  dts |= dts_ext << 24;
 
   if (type == 9) {
     data += 7;
@@ -1547,10 +1825,10 @@ gst_flv_demux_parse_tag_timestamp (GstFlvDemux * demux, gboolean index,
     keyframe = ((data[0] >> 4) == 1);
   }
 
-  ret = pts * GST_MSECOND;
-  GST_LOG_OBJECT (demux, "pts: %" GST_TIME_FORMAT, GST_TIME_ARGS (ret));
+  ret = dts * GST_MSECOND;
+  GST_LOG_OBJECT (demux, "dts: %" GST_TIME_FORMAT, GST_TIME_ARGS (ret));
 
-  if (index && demux->index && !demux->indexed && (type == 9 || (type == 8
+  if (index && !demux->indexed && (type == 9 || (type == 8
               && !demux->has_video))) {
     gst_flv_demux_parse_and_add_index_entry (demux, ret, demux->offset,
         keyframe);
@@ -1577,6 +1855,16 @@ gst_flv_demux_parse_tag_type (GstFlvDemux * demux, GstBuffer * buffer)
 
   tag_type = map.data[0];
 
+  /* Tag size is 1 byte of type + 3 bytes of size + 7 bytes + tag data size +
+   * 4 bytes of previous tag size */
+  demux->tag_data_size = GST_READ_UINT24_BE (map.data + 1);
+  demux->tag_size = demux->tag_data_size + 11;
+
+  GST_LOG_OBJECT (demux, "tag data size is %" G_GUINT64_FORMAT,
+      demux->tag_data_size);
+
+  gst_buffer_unmap (buffer, &map);
+
   switch (tag_type) {
     case 9:
       demux->state = FLV_STATE_TAG_VIDEO;
@@ -1591,18 +1879,9 @@ gst_flv_demux_parse_tag_type (GstFlvDemux * demux, GstBuffer * buffer)
       break;
     default:
       GST_WARNING_OBJECT (demux, "unsupported tag type %u", tag_type);
+      demux->state = FLV_STATE_SKIP;
   }
 
-  /* Tag size is 1 byte of type + 3 bytes of size + 7 bytes + tag data size +
-   * 4 bytes of previous tag size */
-  demux->tag_data_size = GST_READ_UINT24_BE (map.data + 1);
-  demux->tag_size = demux->tag_data_size + 11;
-
-  GST_LOG_OBJECT (demux, "tag data size is %" G_GUINT64_FORMAT,
-      demux->tag_data_size);
-
-  gst_buffer_unmap (buffer, &map);
-
   return ret;
 }
 
@@ -1627,6 +1906,17 @@ gst_flv_demux_parse_header (GstFlvDemux * demux, GstBuffer * buffer)
     }
   }
 
+  if (map.data[3] == '1') {
+    GST_DEBUG_OBJECT (demux, "FLV version 1 detected");
+  } else {
+    if (G_UNLIKELY (demux->strict)) {
+      GST_WARNING_OBJECT (demux, "invalid header version detected");
+      ret = GST_FLOW_EOS;
+      goto beach;
+    }
+
+  }
+
   /* Now look at audio/video flags */
   {
     guint8 flags = map.data[4];
@@ -1683,6 +1973,9 @@ gst_flv_demux_cleanup (GstFlvDemux * demux)
 
   demux->state = FLV_STATE_HEADER;
 
+  demux->have_group_id = FALSE;
+  demux->group_id = G_MAXUINT;
+
   demux->flushing = FALSE;
   demux->need_header = TRUE;
   demux->audio_need_segment = TRUE;
@@ -1690,13 +1983,8 @@ gst_flv_demux_cleanup (GstFlvDemux * demux)
   demux->audio_need_discont = TRUE;
   demux->video_need_discont = TRUE;
 
-  /* By default we consider them as linked */
-  demux->audio_linked = TRUE;
-  demux->video_linked = TRUE;
-
   demux->has_audio = FALSE;
   demux->has_video = FALSE;
-  demux->push_tags = FALSE;
   demux->got_par = FALSE;
 
   demux->indexed = FALSE;
@@ -1707,11 +1995,16 @@ gst_flv_demux_cleanup (GstFlvDemux * demux)
   demux->index_max_time = 0;
 
   demux->audio_start = demux->video_start = GST_CLOCK_TIME_NONE;
-  demux->last_audio_pts = demux->last_video_pts = 0;
+  demux->last_audio_pts = demux->last_video_dts = 0;
   demux->audio_time_offset = demux->video_time_offset = 0;
 
   demux->no_more_pads = FALSE;
 
+#ifndef GST_DISABLE_DEBUG
+  demux->no_audio_warned = FALSE;
+  demux->no_video_warned = FALSE;
+#endif
+
   gst_segment_init (&demux->segment, GST_FORMAT_TIME);
 
   demux->w = demux->h = 0;
@@ -1741,12 +2034,14 @@ gst_flv_demux_cleanup (GstFlvDemux * demux)
   }
 
   if (demux->audio_pad) {
+    gst_flow_combiner_remove_pad (demux->flowcombiner, demux->audio_pad);
     gst_element_remove_pad (GST_ELEMENT (demux), demux->audio_pad);
     gst_object_unref (demux->audio_pad);
     demux->audio_pad = NULL;
   }
 
   if (demux->video_pad) {
+    gst_flow_combiner_remove_pad (demux->flowcombiner, demux->video_pad);
     gst_element_remove_pad (GST_ELEMENT (demux), demux->video_pad);
     gst_object_unref (demux->video_pad);
     demux->video_pad = NULL;
@@ -1761,6 +2056,8 @@ gst_flv_demux_cleanup (GstFlvDemux * demux)
     g_array_free (demux->filepositions, TRUE);
     demux->filepositions = NULL;
   }
+
+  gst_flv_demux_clear_tags (demux);
 }
 
 /*
@@ -1826,13 +2123,8 @@ gst_flv_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
 
 parse:
   if (G_UNLIKELY (ret != GST_FLOW_OK)) {
-    if (ret == GST_FLOW_NOT_LINKED && (demux->audio_linked
-            || demux->video_linked)) {
-      ret = GST_FLOW_OK;
-    } else {
-      GST_DEBUG_OBJECT (demux, "got flow return %s", gst_flow_get_name (ret));
-      goto beach;
-    }
+    GST_DEBUG_OBJECT (demux, "got flow return %s", gst_flow_get_name (ret));
+    goto beach;
   }
 
   if (G_UNLIKELY (demux->flushing)) {
@@ -1991,18 +2283,21 @@ parse:
       demux->state = FLV_STATE_TAG_TYPE;
       goto beach;
     }
+    case FLV_STATE_SKIP:
+      /* Skip unknown tags (set in _parse_tag_type()) */
+      if (gst_adapter_available (demux->adapter) >= demux->tag_size) {
+        gst_adapter_flush (demux->adapter, demux->tag_size);
+        demux->offset += demux->tag_size;
+        demux->state = FLV_STATE_TAG_TYPE;
+        goto parse;
+      } else {
+        goto beach;
+      }
     default:
       GST_DEBUG_OBJECT (demux, "unexpected demuxer state");
   }
 
 beach:
-  if (G_UNLIKELY (ret == GST_FLOW_NOT_LINKED)) {
-    /* If either audio or video is linked we return GST_FLOW_OK */
-    if (demux->audio_linked || demux->video_linked) {
-      ret = GST_FLOW_OK;
-    }
-  }
-
   return ret;
 
 /* ERRORS */
@@ -2111,13 +2406,8 @@ gst_flv_demux_pull_tag (GstPad * pad, GstFlvDemux * demux)
   demux->state = FLV_STATE_TAG_TYPE;
 
   if (G_UNLIKELY (ret == GST_FLOW_NOT_LINKED)) {
-    /* If either audio or video is linked we return GST_FLOW_OK */
-    if (demux->audio_linked || demux->video_linked) {
-      ret = GST_FLOW_OK;
-    } else {
-      GST_WARNING_OBJECT (demux, "parsing this tag returned not-linked and "
-          "neither video nor audio are linked");
-    }
+    GST_WARNING_OBJECT (demux, "parsing this tag returned not-linked and "
+        "neither video nor audio are linked");
   }
 
 beach:
@@ -2179,6 +2469,7 @@ static GstFlowReturn
 gst_flv_demux_seek_to_prev_keyframe (GstFlvDemux * demux)
 {
   GstFlowReturn ret = GST_FLOW_EOS;
+  GstIndex *index;
   GstIndexEntry *entry = NULL;
 
   GST_DEBUG_OBJECT (demux,
@@ -2197,26 +2488,31 @@ gst_flv_demux_seek_to_prev_keyframe (GstFlvDemux * demux)
 
   GST_DEBUG_OBJECT (demux, "locating previous position");
 
+  index = gst_flv_demux_get_index (GST_ELEMENT (demux));
+
   /* locate index entry before previous start position */
-  if (demux->index)
-    entry = gst_index_get_assoc_entry (demux->index, demux->index_id,
+  if (index) {
+    entry = gst_index_get_assoc_entry (index, demux->index_id,
         GST_INDEX_LOOKUP_BEFORE, GST_ASSOCIATION_FLAG_KEY_UNIT,
         GST_FORMAT_BYTES, demux->from_offset - 1);
 
-  if (entry) {
-    gint64 bytes = 0, time = 0;
+    if (entry) {
+      gint64 bytes = 0, time = 0;
 
-    gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &bytes);
-    gst_index_entry_assoc_map (entry, GST_FORMAT_TIME, &time);
+      gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &bytes);
+      gst_index_entry_assoc_map (entry, GST_FORMAT_TIME, &time);
 
-    GST_DEBUG_OBJECT (demux, "found index entry for %" G_GINT64_FORMAT
-        " at %" GST_TIME_FORMAT ", seeking to %" G_GINT64_FORMAT,
-        demux->offset - 1, GST_TIME_ARGS (time), bytes);
+      GST_DEBUG_OBJECT (demux, "found index entry for %" G_GINT64_FORMAT
+          " at %" GST_TIME_FORMAT ", seeking to %" G_GINT64_FORMAT,
+          demux->offset - 1, GST_TIME_ARGS (time), bytes);
 
-    /* setup for next section */
-    demux->to_offset = demux->from_offset;
-    gst_flv_demux_move_to_offset (demux, bytes, FALSE);
-    ret = GST_FLOW_OK;
+      /* setup for next section */
+      demux->to_offset = demux->from_offset;
+      gst_flv_demux_move_to_offset (demux, bytes, FALSE);
+      ret = GST_FLOW_OK;
+    }
+
+    gst_object_unref (index);
   }
 
 done:
@@ -2297,6 +2593,9 @@ gst_flv_demux_get_metadata (GstFlvDemux * demux)
   gst_buffer_unref (buffer);
   buffer = NULL;
 
+  if (G_UNLIKELY (offset < tag_size))
+    goto exit;
+
   offset -= tag_size;
   if (GST_FLOW_OK != gst_flv_demux_pull_range (demux, demux->sinkpad, offset,
           12, &buffer))
@@ -2397,7 +2696,8 @@ gst_flv_demux_loop (GstPad * pad)
   }
 
   /* pause if something went wrong or at end */
-  if (G_UNLIKELY (ret != GST_FLOW_OK))
+  if (G_UNLIKELY (ret != GST_FLOW_OK) && !(ret == GST_FLOW_NOT_LINKED
+          && !demux->no_more_pads))
     goto pause;
 
   gst_object_unref (demux);
@@ -2427,7 +2727,7 @@ pause:
         demux->no_more_pads = TRUE;
       }
 
-      if (demux->segment.flags & GST_SEEK_FLAG_SEGMENT) {
+      if (demux->segment.flags & GST_SEGMENT_FLAG_SEGMENT) {
         gint64 stop;
 
         /* for segment playback we need to post when (in stream time)
@@ -2440,12 +2740,17 @@ pause:
           gst_element_post_message (GST_ELEMENT_CAST (demux),
               gst_message_new_segment_done (GST_OBJECT_CAST (demux),
                   GST_FORMAT_TIME, stop));
+          gst_flv_demux_push_src_event (demux,
+              gst_event_new_segment_done (GST_FORMAT_TIME, stop));
         } else {                /* Reverse playback */
           GST_LOG_OBJECT (demux, "Sending segment done, at beginning of "
               "segment");
           gst_element_post_message (GST_ELEMENT_CAST (demux),
               gst_message_new_segment_done (GST_OBJECT_CAST (demux),
                   GST_FORMAT_TIME, demux->segment.start));
+          gst_flv_demux_push_src_event (demux,
+              gst_event_new_segment_done (GST_FORMAT_TIME,
+                  demux->segment.start));
         }
       } else {
         /* normal playback, send EOS to all linked pads */
@@ -2455,13 +2760,14 @@ pause:
         }
 
         GST_LOG_OBJECT (demux, "Sending EOS, at end of stream");
-        if (!gst_flv_demux_push_src_event (demux, gst_event_new_eos ()))
+        if (!demux->audio_pad && !demux->video_pad)
+          GST_ELEMENT_ERROR (demux, STREAM, FAILED,
+              ("Internal data stream error."), ("Got EOS before any data"));
+        else if (!gst_flv_demux_push_src_event (demux, gst_event_new_eos ()))
           GST_WARNING_OBJECT (demux, "failed pushing EOS on streams");
       }
     } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) {
-      GST_ELEMENT_ERROR (demux, STREAM, FAILED,
-          ("Internal data stream error."),
-          ("stream stopped, reason %s", reason));
+      GST_ELEMENT_FLOW_ERROR (demux, ret);
       gst_flv_demux_push_src_event (demux, gst_event_new_eos ());
     }
     gst_object_unref (demux);
@@ -2470,21 +2776,26 @@ pause:
 }
 
 static guint64
-gst_flv_demux_find_offset (GstFlvDemux * demux, GstSegment * segment)
+gst_flv_demux_find_offset (GstFlvDemux * demux, GstSegment * segment,
+    GstSeekFlags seek_flags)
 {
   gint64 bytes = 0;
   gint64 time = 0;
+  GstIndex *index;
   GstIndexEntry *entry;
 
   g_return_val_if_fail (segment != NULL, 0);
 
   time = segment->position;
 
-  if (demux->index) {
+  index = gst_flv_demux_get_index (GST_ELEMENT (demux));
+
+  if (index) {
     /* Let's check if we have an index entry for that seek time */
-    entry = gst_index_get_assoc_entry (demux->index, demux->index_id,
-        GST_INDEX_LOOKUP_BEFORE, GST_ASSOCIATION_FLAG_KEY_UNIT,
-        GST_FORMAT_TIME, time);
+    entry = gst_index_get_assoc_entry (index, demux->index_id,
+        seek_flags & GST_SEEK_FLAG_SNAP_AFTER ?
+        GST_INDEX_LOOKUP_AFTER : GST_INDEX_LOOKUP_BEFORE,
+        GST_ASSOCIATION_FLAG_KEY_UNIT, GST_FORMAT_TIME, time);
 
     if (entry) {
       gst_index_entry_assoc_map (entry, GST_FORMAT_BYTES, &bytes);
@@ -2495,17 +2806,17 @@ gst_flv_demux_find_offset (GstFlvDemux * demux, GstSegment * segment)
           GST_TIME_ARGS (segment->position), GST_TIME_ARGS (time), bytes);
 
       /* Key frame seeking */
-      if (segment->flags & GST_SEEK_FLAG_KEY_UNIT) {
+      if (seek_flags & GST_SEEK_FLAG_KEY_UNIT) {
         /* Adjust the segment so that the keyframe fits in */
-        if (time < segment->start) {
-          segment->start = segment->time = time;
-        }
+        segment->start = segment->time = time;
         segment->position = time;
       }
     } else {
       GST_DEBUG_OBJECT (demux, "no index entry found for %" GST_TIME_FORMAT,
           GST_TIME_ARGS (segment->start));
     }
+
+    gst_object_unref (index);
   }
 
   return bytes;
@@ -2529,7 +2840,6 @@ flv_demux_handle_seek_push (GstFlvDemux * demux, GstEvent * event)
     goto wrong_format;
 
   flush = ! !(flags & GST_SEEK_FLAG_FLUSH);
-  /* FIXME : the keyframe flag is never used ! */
 
   /* Work on a copy until we are sure the seek succeeded. */
   memcpy (&seeksegment, &demux->segment, sizeof (GstSegment));
@@ -2546,18 +2856,19 @@ flv_demux_handle_seek_push (GstFlvDemux * demux, GstEvent * event)
 
   if (flush || seeksegment.position != demux->segment.position) {
     /* Do the actual seeking */
-    guint64 offset = gst_flv_demux_find_offset (demux, &seeksegment);
+    guint64 offset = gst_flv_demux_find_offset (demux, &seeksegment, flags);
 
     GST_DEBUG_OBJECT (demux, "generating an upstream seek at position %"
         G_GUINT64_FORMAT, offset);
     ret = gst_pad_push_event (demux->sinkpad,
         gst_event_new_seek (seeksegment.rate, GST_FORMAT_BYTES,
-            seeksegment.flags | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET,
+            flags | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET,
             offset, GST_SEEK_TYPE_NONE, 0));
     if (G_UNLIKELY (!ret)) {
       GST_WARNING_OBJECT (demux, "upstream seek failed");
     }
 
+    gst_flow_combiner_reset (demux->flowcombiner);
     /* Tell all the stream we moved to a different position (discont) */
     demux->audio_need_discont = TRUE;
     demux->video_need_discont = TRUE;
@@ -2578,6 +2889,11 @@ flv_demux_handle_seek_push (GstFlvDemux * demux, GstEvent * event)
       gst_event_unref (demux->new_seg_event);
       demux->new_seg_event = NULL;
     }
+    GST_DEBUG_OBJECT (demux, "preparing newsegment from %"
+        GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (demux->segment.start),
+        GST_TIME_ARGS (demux->segment.stop));
+    demux->new_seg_event = gst_event_new_segment (&demux->segment);
     gst_event_unref (event);
   } else {
     ret = gst_pad_push_event (demux->sinkpad, event);
@@ -2690,7 +3006,6 @@ gst_flv_demux_handle_seek_pull (GstFlvDemux * demux, GstEvent * event,
   GST_OBJECT_UNLOCK (demux);
 
   flush = ! !(flags & GST_SEEK_FLAG_FLUSH);
-  /* FIXME : the keyframe flag is never used */
 
   if (flush) {
     /* Flush start up and downstream to make sure data flow and loops are
@@ -2745,9 +3060,10 @@ gst_flv_demux_handle_seek_pull (GstFlvDemux * demux, GstEvent * event,
       ret = TRUE;
       goto exit;
     }
+
     /* now index should be as reliable as it can be for current purpose */
     gst_flv_demux_move_to_offset (demux,
-        gst_flv_demux_find_offset (demux, &seeksegment), TRUE);
+        gst_flv_demux_find_offset (demux, &seeksegment, flags), TRUE);
     ret = TRUE;
   } else {
     ret = TRUE;
@@ -2763,12 +3079,13 @@ gst_flv_demux_handle_seek_pull (GstFlvDemux * demux, GstEvent * event,
     memcpy (&demux->segment, &seeksegment, sizeof (GstSegment));
 
     /* Notify about the start of a new segment */
-    if (demux->segment.flags & GST_SEEK_FLAG_SEGMENT) {
+    if (demux->segment.flags & GST_SEGMENT_FLAG_SEGMENT) {
       gst_element_post_message (GST_ELEMENT (demux),
           gst_message_new_segment_start (GST_OBJECT (demux),
               demux->segment.format, demux->segment.position));
     }
 
+    gst_flow_combiner_reset (demux->flowcombiner);
     /* Tell all the stream a new segment is needed */
     demux->audio_need_segment = TRUE;
     demux->video_need_segment = TRUE;
@@ -2778,15 +3095,11 @@ gst_flv_demux_handle_seek_pull (GstFlvDemux * demux, GstEvent * event,
       gst_event_unref (demux->new_seg_event);
       demux->new_seg_event = NULL;
     }
-    if (demux->segment.rate < 0.0) {
-      /* we can't generate a segment by locking on
-       * to the first timestamp we see */
-      GST_DEBUG_OBJECT (demux, "preparing newsegment from %"
-          GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
-          GST_TIME_ARGS (demux->segment.start),
-          GST_TIME_ARGS (demux->segment.stop));
-      demux->new_seg_event = gst_event_new_segment (&demux->segment);
-    }
+    GST_DEBUG_OBJECT (demux, "preparing newsegment from %"
+        GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (demux->segment.start),
+        GST_TIME_ARGS (demux->segment.stop));
+    demux->new_seg_event = gst_event_new_segment (&demux->segment);
   }
 
 exit:
@@ -2802,7 +3115,7 @@ exit:
     gst_pad_pause_task (demux->sinkpad);
   } else {
     gst_pad_start_task (demux->sinkpad,
-        (GstTaskFunction) gst_flv_demux_loop, demux->sinkpad);
+        (GstTaskFunction) gst_flv_demux_loop, demux->sinkpad, NULL);
   }
 
   GST_PAD_STREAM_UNLOCK (demux->sinkpad);
@@ -2833,7 +3146,8 @@ gst_flv_demux_sink_activate (GstPad * sinkpad, GstObject * parent)
     goto activate_push;
   }
 
-  pull_mode = gst_query_has_scheduling_mode (query, GST_PAD_MODE_PULL);
+  pull_mode = gst_query_has_scheduling_mode_with_flags (query,
+      GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE);
   gst_query_unref (query);
 
   if (!pull_mode)
@@ -2867,7 +3181,7 @@ gst_flv_demux_sink_activate_mode (GstPad * sinkpad, GstObject * parent,
       if (active) {
         demux->random_access = TRUE;
         res = gst_pad_start_task (sinkpad, (GstTaskFunction) gst_flv_demux_loop,
-            sinkpad);
+            sinkpad, NULL);
       } else {
         demux->random_access = FALSE;
         res = gst_pad_stop_task (sinkpad);
@@ -2902,20 +3216,35 @@ gst_flv_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
       ret = gst_flv_demux_push_src_event (demux, event);
       break;
     case GST_EVENT_EOS:
+    {
+      GstIndex *index;
+
       GST_DEBUG_OBJECT (demux, "received EOS");
-      if (demux->index) {
+
+      index = gst_flv_demux_get_index (GST_ELEMENT (demux));
+
+      if (index) {
         GST_DEBUG_OBJECT (demux, "committing index");
-        gst_index_commit (demux->index, demux->index_id);
-      }
-      if (!demux->no_more_pads) {
-        gst_element_no_more_pads (GST_ELEMENT (demux));
-        demux->no_more_pads = TRUE;
+        gst_index_commit (index, demux->index_id);
+        gst_object_unref (index);
       }
 
-      if (!gst_flv_demux_push_src_event (demux, event))
-        GST_WARNING_OBJECT (demux, "failed pushing EOS on streams");
+      if (!demux->audio_pad && !demux->video_pad) {
+        GST_ELEMENT_ERROR (demux, STREAM, FAILED,
+            ("Internal data stream error."), ("Got EOS before any data"));
+        gst_event_unref (event);
+      } else {
+        if (!demux->no_more_pads) {
+          gst_element_no_more_pads (GST_ELEMENT (demux));
+          demux->no_more_pads = TRUE;
+        }
+
+        if (!gst_flv_demux_push_src_event (demux, event))
+          GST_WARNING_OBJECT (demux, "failed pushing EOS on streams");
+      }
       ret = TRUE;
       break;
+    }
     case GST_EVENT_SEGMENT:
     {
       GstSegment in_segment;
@@ -2939,11 +3268,16 @@ gst_flv_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
         demux->video_need_segment = TRUE;
         ret = TRUE;
         gst_event_unref (event);
+        if (demux->new_seg_event) {
+          gst_event_unref (demux->new_seg_event);
+          demux->new_seg_event = NULL;
+        }
       }
+      gst_flow_combiner_reset (demux->flowcombiner);
       break;
     }
     default:
-      ret = gst_flv_demux_push_src_event (demux, event);
+      ret = gst_pad_event_default (pad, parent, event);
       break;
   }
 
@@ -2962,6 +3296,13 @@ gst_flv_demux_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
 
   switch (GST_EVENT_TYPE (event)) {
     case GST_EVENT_SEEK:
+      /* Try to push upstream first */
+      gst_event_ref (event);
+      ret = gst_pad_push_event (demux->sinkpad, event);
+      if (ret) {
+        gst_event_unref (event);
+        break;
+      }
       if (demux->random_access) {
         ret = gst_flv_demux_handle_seek_pull (demux, event, TRUE);
       } else {
@@ -2999,11 +3340,16 @@ gst_flv_demux_query (GstPad * pad, GstObject * parent, GstQuery * query)
         goto beach;
       }
 
+      /* Try to push upstream first */
+      res = gst_pad_peer_query (demux->sinkpad, query);
+      if (res)
+        goto beach;
+
       GST_DEBUG_OBJECT (pad, "duration query, replying %" GST_TIME_FORMAT,
           GST_TIME_ARGS (demux->duration));
 
       gst_query_set_duration (query, GST_FORMAT_TIME, demux->duration);
-
+      res = TRUE;
       break;
     }
     case GST_QUERY_POSITION:
@@ -3044,6 +3390,7 @@ gst_flv_demux_query (GstPad * pad, GstObject * parent, GstQuery * query)
         }
       }
       res = TRUE;
+      /* FIXME, check index this way is not thread safe */
       if (fmt != GST_FORMAT_TIME || !demux->index) {
         gst_query_set_seeking (query, fmt, FALSE, -1, -1);
       } else if (demux->random_access) {
@@ -3065,6 +3412,25 @@ gst_flv_demux_query (GstPad * pad, GstObject * parent, GstQuery * query)
       }
       break;
     }
+    case GST_QUERY_SEGMENT:
+    {
+      GstFormat format;
+      gint64 start, stop;
+
+      format = demux->segment.format;
+
+      start =
+          gst_segment_to_stream_time (&demux->segment, format,
+          demux->segment.start);
+      if ((stop = demux->segment.stop) == -1)
+        stop = demux->segment.duration;
+      else
+        stop = gst_segment_to_stream_time (&demux->segment, format, stop);
+
+      gst_query_set_segment (query, demux->segment.rate, format, start, stop);
+      res = TRUE;
+      break;
+    }
     case GST_QUERY_LATENCY:
     default:
       res = gst_pad_query_default (pad, parent, query);
@@ -3130,23 +3496,34 @@ static void
 gst_flv_demux_set_index (GstElement * element, GstIndex * index)
 {
   GstFlvDemux *demux = GST_FLV_DEMUX (element);
+  GstIndex *old_index;
 
   GST_OBJECT_LOCK (demux);
-  if (demux->index)
-    gst_object_unref (demux->index);
+
+  old_index = demux->index;
+
   if (index) {
     demux->index = gst_object_ref (index);
     demux->own_index = FALSE;
   } else
     demux->index = NULL;
 
+  if (old_index)
+    gst_object_unref (demux->index);
+
+  gst_object_ref (index);
+
   GST_OBJECT_UNLOCK (demux);
+
   /* object lock might be taken again */
   if (index)
     gst_index_get_writer_id (index, GST_OBJECT (element), &demux->index_id);
+
   GST_DEBUG_OBJECT (demux, "Set index %" GST_PTR_FORMAT, demux->index);
 
+  gst_object_unref (index);
 }
+#endif
 
 static GstIndex *
 gst_flv_demux_get_index (GstElement * element)
@@ -3162,7 +3539,6 @@ gst_flv_demux_get_index (GstElement * element)
 
   return result;
 }
-#endif
 
 static void
 gst_flv_demux_dispose (GObject * object)
@@ -3178,10 +3554,25 @@ gst_flv_demux_dispose (GObject * object)
   }
 
   if (demux->taglist) {
-    gst_tag_list_free (demux->taglist);
+    gst_tag_list_unref (demux->taglist);
     demux->taglist = NULL;
   }
 
+  if (demux->audio_tags) {
+    gst_tag_list_unref (demux->audio_tags);
+    demux->audio_tags = NULL;
+  }
+
+  if (demux->video_tags) {
+    gst_tag_list_unref (demux->video_tags);
+    demux->video_tags = NULL;
+  }
+
+  if (demux->flowcombiner) {
+    gst_flow_combiner_free (demux->flowcombiner);
+    demux->flowcombiner = NULL;
+  }
+
   if (demux->new_seg_event) {
     gst_event_unref (demux->new_seg_event);
     demux->new_seg_event = NULL;
@@ -3241,15 +3632,14 @@ gst_flv_demux_class_init (GstFlvDemuxClass * klass)
   gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_flv_demux_get_index);
 #endif
 
-  gst_element_class_add_pad_template (gstelement_class,
-      gst_static_pad_template_get (&flv_sink_template));
-  gst_element_class_add_pad_template (gstelement_class,
-      gst_static_pad_template_get (&audio_src_template));
-  gst_element_class_add_pad_template (gstelement_class,
-      gst_static_pad_template_get (&video_src_template));
+  gst_element_class_add_static_pad_template (gstelement_class,
+      &flv_sink_template);
+  gst_element_class_add_static_pad_template (gstelement_class,
+      &audio_src_template);
+  gst_element_class_add_static_pad_template (gstelement_class,
+      &video_src_template);
   gst_element_class_set_static_metadata (gstelement_class, "FLV Demuxer",
-      "Codec/Demuxer",
-      "Demux FLV feeds into digital streams",
+      "Codec/Demuxer", "Demux FLV feeds into digital streams",
       "Julien Moutte <julien@moutte.net>");
 }
 
@@ -3271,8 +3661,7 @@ gst_flv_demux_init (GstFlvDemux * demux)
   gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad);
 
   demux->adapter = gst_adapter_new ();
-  demux->taglist = gst_tag_list_new_empty ();
-  gst_segment_init (&demux->segment, GST_FORMAT_TIME);
+  demux->flowcombiner = gst_flow_combiner_new ();
 
   demux->own_index = FALSE;