Merge branch 'upstream/1.16' into tizen_gst_1.16.2
[platform/upstream/gst-plugins-good.git] / gst / flv / gstflvdemux.c
index 2ca73f5..283a76c 100644 (file)
 #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"
@@ -68,8 +70,8 @@ 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-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;")
     );
 
@@ -112,6 +114,8 @@ static gboolean gst_flv_demux_src_event (GstPad * pad, GstObject * parent,
 
 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)
@@ -242,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);
 
-    d = g_ascii_strtoull (tokens[2], &endptr, 10);
-    if (d == 0 && *endptr != '\0')
-      goto out;
+  g_free (stripped);
 
-    g_date_set_day (date, d);
+  if (g_strv_length (tokens) != 5)
+    goto out;
 
-    d = g_ascii_strtoull (tokens[4], &endptr, 10);
-    if (d == 0 && *endptr != '\0')
-      goto out;
+  /* year */
+  d = g_ascii_strtoull (tokens[4], &endptr, 10);
+  if (d == 0 && *endptr != '\0')
+    goto out;
 
-    g_date_set_year (date, d);
+  year = d;
 
-  out:
-    if (tokens)
-      g_strfreev (tokens);
+  /* 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;
+
+  /* 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
@@ -343,6 +370,12 @@ 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);
       }
@@ -373,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 {
@@ -536,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)
 {
@@ -569,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;
@@ -611,7 +669,7 @@ 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);
@@ -664,9 +722,9 @@ gst_flv_demux_audio_negotiate (GstFlvDemux * demux, guint32 codec_tag,
     guint32 rate, guint32 channels, guint32 width)
 {
   GstCaps *caps = NULL, *old_caps;
-  gchar *codec_name = NULL;
   gboolean ret = FALSE;
   guint adjusted_rate = rate;
+  guint adjusted_channels = channels;
   GstEvent *event;
   gchar *stream_id;
 
@@ -728,6 +786,16 @@ gst_flv_demux_audio_negotiate (GstFlvDemux * demux, guint32 codec_tag,
         } 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);
 
@@ -749,6 +817,7 @@ gst_flv_demux_audio_negotiate (GstFlvDemux * demux, guint32 codec_tag,
       GstByteWriter w;
       GstStructure *structure;
       GstBuffer *buf;
+      GstTagList *tags;
 
       caps = gst_caps_new_empty_simple ("audio/x-speex");
       structure = gst_caps_get_structure (caps, 0);
@@ -785,7 +854,9 @@ gst_flv_demux_audio_negotiate (GstFlvDemux * demux, guint32 codec_tag,
 
       /* comment part */
       g_value_init (&value, GST_TYPE_BUFFER);
-      buf = gst_buffer_new_wrapped (g_memdup ("No comments", 12), 12);
+      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);
@@ -807,7 +878,7 @@ 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,
@@ -843,18 +914,10 @@ done:
     demux->width = width;
 
     if (caps) {
-      codec_name = gst_pb_utils_get_codec_description (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_DEBUG_OBJECT (demux->audio_pad, "successfully negotiated caps %"
           GST_PTR_FORMAT, caps);
+
+      gst_flv_demux_push_tags (demux);
     } else {
       GST_DEBUG_OBJECT (demux->audio_pad, "delayed setting caps");
     }
@@ -887,25 +950,58 @@ 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_tag_list_set_scope (demux->taglist, GST_TAG_SCOPE_GLOBAL);
-    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)));
   }
 }
 
@@ -944,18 +1040,16 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer)
 
   GST_LOG_OBJECT (demux, "parsing an audio tag");
 
-  if G_UNLIKELY
-    (!demux->audio_pad && demux->no_more_pads) {
+  if (G_UNLIKELY (!demux->audio_pad && demux->no_more_pads)) {
 #ifndef GST_DISABLE_DEBUG
-    if G_UNLIKELY
-      (!demux->no_audio_warned) {
+    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;
-    }
+  }
 
   g_return_val_if_fail (gst_buffer_get_size (buffer) == demux->tag_size,
       GST_FLOW_ERROR);
@@ -1012,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");
@@ -1076,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;
     }
   }
 
@@ -1095,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");
@@ -1109,38 +1233,6 @@ 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;
-      }
-      case 1:
-        if (!demux->audio_codec_data) {
-          GST_ERROR_OBJECT (demux, "got AAC audio packet before codec data");
-          ret = GST_FLOW_OK;
-          gst_buffer_unref (outbuf);
-          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);
-    }
-  }
-
   /* detect (and deem to be resyncs)  large pts gaps */
   if (gst_flv_demux_update_resync (demux, pts, demux->audio_need_discont,
           &demux->last_audio_pts, &demux->audio_time_offset)) {
@@ -1212,7 +1304,6 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer)
         " 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 */
@@ -1230,7 +1321,8 @@ gst_flv_demux_parse_tag_audio (GstFlvDemux * demux, GstBuffer * buffer)
     goto beach;
   }
 
-  ret = gst_flow_combiner_update_flow (demux->flowcombiner, ret);
+  ret = gst_flow_combiner_update_pad_flow (demux->flowcombiner,
+      demux->audio_pad, ret);
 
 beach:
   gst_buffer_unmap (buffer, &map);
@@ -1243,7 +1335,6 @@ gst_flv_demux_video_negotiate (GstFlvDemux * demux, guint32 codec_tag)
 {
   gboolean ret = FALSE;
   GstCaps *caps = NULL, *old_caps;
-  gchar *codec_name = NULL;
   GstEvent *event;
   gchar *stream_id;
 
@@ -1273,6 +1364,18 @@ gst_flv_demux_video_negotiate (GstFlvDemux * demux, guint32 codec_tag)
           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);
   }
@@ -1282,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);
@@ -1336,18 +1441,10 @@ done:
     demux->video_codec_tag = codec_tag;
 
     if (caps) {
-      codec_name = gst_pb_utils_get_codec_description (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_DEBUG_OBJECT (demux->video_pad, "successfully negotiated caps %"
           GST_PTR_FORMAT, caps);
+
+      gst_flv_demux_push_tags (demux);
     } else {
       GST_DEBUG_OBJECT (demux->video_pad, "delayed setting caps");
     }
@@ -1428,12 +1525,57 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
     cts = GST_READ_UINT24_BE (data + 9);
     cts = (cts + 0xff800000) ^ 0xff800000;
 
+    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;
+    }
+
     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 =
@@ -1492,7 +1634,6 @@ 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;
     }
   }
 
@@ -1511,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");
@@ -1525,38 +1662,6 @@ 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;
-      }
-      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;
-          gst_buffer_unref (outbuf);
-          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);
-    }
-  }
-
   /* 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)) {
@@ -1633,7 +1738,6 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
         " 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 */
@@ -1651,7 +1755,8 @@ gst_flv_demux_parse_tag_video (GstFlvDemux * demux, GstBuffer * buffer)
     goto beach;
   }
 
-  ret = gst_flow_combiner_update_flow (demux->flowcombiner, ret);
+  ret = gst_flow_combiner_update_pad_flow (demux->flowcombiner,
+      demux->video_pad, ret);
 
 beach:
   gst_buffer_unmap (buffer, &map);
@@ -1880,7 +1985,6 @@ gst_flv_demux_cleanup (GstFlvDemux * demux)
 
   demux->has_audio = FALSE;
   demux->has_video = FALSE;
-  demux->push_tags = FALSE;
   demux->got_par = FALSE;
 
   demux->indexed = FALSE;
@@ -1952,6 +2056,8 @@ gst_flv_demux_cleanup (GstFlvDemux * demux)
     g_array_free (demux->filepositions, TRUE);
     demux->filepositions = NULL;
   }
+
+  gst_flv_demux_clear_tags (demux);
 }
 
 /*
@@ -2487,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))
@@ -2587,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);
@@ -2617,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)
@@ -2657,9 +2767,7 @@ pause:
           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);
@@ -2668,7 +2776,8 @@ 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;
@@ -2684,8 +2793,9 @@ gst_flv_demux_find_offset (GstFlvDemux * demux, GstSegment * segment)
   if (index) {
     /* Let's check if we have an index entry for that seek time */
     entry = gst_index_get_assoc_entry (index, demux->index_id,
-        GST_INDEX_LOOKUP_BEFORE, GST_ASSOCIATION_FLAG_KEY_UNIT,
-        GST_FORMAT_TIME, time);
+        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);
@@ -2696,11 +2806,9 @@ 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 {
@@ -2732,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));
@@ -2749,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;
@@ -2781,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);
@@ -2893,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
@@ -2948,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;
@@ -2966,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;
@@ -2981,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:
@@ -3119,10 +3229,11 @@ gst_flv_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
         gst_object_unref (index);
       }
 
-      if (!demux->audio_pad && !demux->video_pad)
+      if (!demux->audio_pad && !demux->video_pad) {
         GST_ELEMENT_ERROR (demux, STREAM, FAILED,
             ("Internal data stream error."), ("Got EOS before any data"));
-      else {
+        gst_event_unref (event);
+      } else {
         if (!demux->no_more_pads) {
           gst_element_no_more_pads (GST_ELEMENT (demux));
           demux->no_more_pads = TRUE;
@@ -3162,6 +3273,7 @@ gst_flv_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
           demux->new_seg_event = NULL;
         }
       }
+      gst_flow_combiner_reset (demux->flowcombiner);
       break;
     }
     default:
@@ -3185,9 +3297,12 @@ 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)
+      if (ret) {
+        gst_event_unref (event);
         break;
+      }
       if (demux->random_access) {
         ret = gst_flv_demux_handle_seek_pull (demux, event, TRUE);
       } else {
@@ -3443,6 +3558,16 @@ gst_flv_demux_dispose (GObject * object)
     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;
@@ -3507,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>");
 }
 
@@ -3537,9 +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 ();
   demux->flowcombiner = gst_flow_combiner_new ();
-  gst_segment_init (&demux->segment, GST_FORMAT_TIME);
 
   demux->own_index = FALSE;