Merge branch 'tizen_gst_1.19.3' into tizen_gst_1.20.0
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-good / gst / isomp4 / qtdemux.c
index b74f4ef..ce45048 100644 (file)
@@ -79,7 +79,7 @@
 #include <gst/math-compat.h>
 
 #ifdef HAVE_ZLIB
-# include <zlib.h>
+#include <zlib.h>
 #endif
 
 /* max. size considered 'sane' for non-mdat atoms */
@@ -115,6 +115,34 @@ GST_DEBUG_CATEGORY (qtdemux_debug);
 typedef struct _QtDemuxCencSampleSetInfo QtDemuxCencSampleSetInfo;
 typedef struct _QtDemuxAavdEncryptionInfo QtDemuxAavdEncryptionInfo;
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+typedef struct _QtDemuxSphericalMetadata QtDemuxSphericalMetadata;
+
+struct _QtDemuxSphericalMetadata
+{
+  gboolean is_spherical;
+  gboolean is_stitched;
+  char *stitching_software;
+  char *projection_type;
+  char *stereo_mode;
+  int source_count;
+  int init_view_heading;
+  int init_view_pitch;
+  int init_view_roll;
+  int timestamp;
+  int full_pano_width_pixels;
+  int full_pano_height_pixels;
+  int cropped_area_image_width;
+  int cropped_area_image_height;
+  int cropped_area_left;
+  int cropped_area_top;
+  QTDEMUX_AMBISONIC_TYPE ambisonic_type;
+  QTDEMUX_AMBISONIC_FORMAT ambisonic_format;
+  QTDEMUX_AMBISONIC_ORDER ambisonic_order;
+};
+
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
 /* Macros for converting to/from timescale */
 #define QTSTREAMTIME_TO_GSTTIME(stream, value) (gst_util_uint64_scale((value), GST_SECOND, (stream)->timescale))
 #define GSTTIME_TO_QTSTREAMTIME(stream, value) (gst_util_uint64_scale((value), (stream)->timescale, GST_SECOND))
@@ -384,6 +412,10 @@ static void gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux,
 static void qtdemux_gst_structure_free (GstStructure * gststructure);
 static void gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard);
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+static void gst_tag_register_spherical_tags (void);
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
 static void
 gst_qtdemux_class_init (GstQTDemuxClass * klass)
 {
@@ -407,6 +439,10 @@ gst_qtdemux_class_init (GstQTDemuxClass * klass)
 
   gst_tag_register_musicbrainz_tags ();
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+  gst_tag_register_spherical_tags ();
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
   gst_element_class_add_static_pad_template (gstelement_class,
       &gst_qtdemux_sink_template);
   gst_element_class_add_static_pad_template (gstelement_class,
@@ -447,6 +483,33 @@ gst_qtdemux_init (GstQTDemux * qtdemux)
   qtdemux->old_streams = g_ptr_array_new_with_free_func
       ((GDestroyNotify) gst_qtdemux_stream_unref);
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+  qtdemux->spherical_metadata = (QtDemuxSphericalMetadata *)
+      malloc (sizeof (QtDemuxSphericalMetadata));
+
+  if (qtdemux->spherical_metadata) {
+    qtdemux->spherical_metadata->is_spherical = FALSE;
+    qtdemux->spherical_metadata->is_stitched = FALSE;
+    qtdemux->spherical_metadata->stitching_software = NULL;
+    qtdemux->spherical_metadata->projection_type = NULL;
+    qtdemux->spherical_metadata->stereo_mode = NULL;
+    qtdemux->spherical_metadata->source_count = 0;
+    qtdemux->spherical_metadata->init_view_heading = 0;
+    qtdemux->spherical_metadata->init_view_pitch = 0;
+    qtdemux->spherical_metadata->init_view_roll = 0;
+    qtdemux->spherical_metadata->timestamp = 0;
+    qtdemux->spherical_metadata->full_pano_width_pixels = 0;
+    qtdemux->spherical_metadata->full_pano_height_pixels = 0;
+    qtdemux->spherical_metadata->cropped_area_image_width = 0;
+    qtdemux->spherical_metadata->cropped_area_image_height = 0;
+    qtdemux->spherical_metadata->cropped_area_left = 0;
+    qtdemux->spherical_metadata->cropped_area_top = 0;
+    qtdemux->spherical_metadata->ambisonic_type = QTDEMUX_AMBISONIC_TYPE_UNKNOWN;
+    qtdemux->spherical_metadata->ambisonic_format = QTDEMUX_AMBISONIC_FORMAT_UNKNOWN;
+    qtdemux->spherical_metadata->ambisonic_order = QTDEMUX_AMBISONIC_ORDER_UNKNOWN;
+  }
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
   GST_OBJECT_FLAG_SET (qtdemux, GST_ELEMENT_FLAG_INDEXABLE);
 
   gst_qtdemux_reset (qtdemux, TRUE);
@@ -467,6 +530,20 @@ gst_qtdemux_dispose (GObject * object)
 {
   GstQTDemux *qtdemux = GST_QTDEMUX (object);
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+  if (qtdemux->spherical_metadata) {
+    if (qtdemux->spherical_metadata->stitching_software)
+      free(qtdemux->spherical_metadata->stitching_software);
+    if (qtdemux->spherical_metadata->projection_type)
+      free(qtdemux->spherical_metadata->projection_type);
+    if (qtdemux->spherical_metadata->stereo_mode)
+      free(qtdemux->spherical_metadata->stereo_mode);
+
+    free(qtdemux->spherical_metadata);
+    qtdemux->spherical_metadata = NULL;
+  }
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
   if (qtdemux->adapter) {
     g_object_unref (G_OBJECT (qtdemux->adapter));
     qtdemux->adapter = NULL;
@@ -791,6 +868,12 @@ gst_qtdemux_push_tags (GstQTDemux * qtdemux, QtDemuxStream * stream)
           stream->stream_tags);
       gst_pad_push_event (stream->pad,
           gst_event_new_tag (gst_tag_list_ref (stream->stream_tags)));
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+      /* post message qtdemux tag (for early recive application) */
+      gst_element_post_message (GST_ELEMENT_CAST (qtdemux),
+            gst_message_new_tag (GST_OBJECT_CAST (qtdemux),
+                  gst_tag_list_copy (stream->stream_tags)));
+#endif
     }
 
     if (G_UNLIKELY (stream->send_global_tags)) {
@@ -2683,6 +2766,475 @@ qtdemux_parse_ftyp (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
   }
 }
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+static void
+_get_int_value_from_xml_string (GstQTDemux * qtdemux,
+    const char *xml_str, const char *param_name, int *value)
+{
+  char *value_start, *value_end, *endptr;
+  const short value_length_max = 12;
+  char init_view_ret[12];
+  int value_length = 0;
+  int i = 0;
+
+  value_start = (xml_str && param_name) ? strstr (xml_str, param_name) : NULL;
+
+  if (!value_start) {
+    GST_WARNING_OBJECT (qtdemux, "error: parameter does not exist: %s\n",
+        param_name);
+    return;
+  }
+
+  value_start += strlen (param_name);
+  while ((value_start[0] == ' ') || (value_start[0] == '\t'))
+    value_start++;
+
+  value_end = strchr (value_start, '<');
+  if (!value_end) {
+    GST_ERROR_OBJECT (qtdemux, "error: incorrect XML\n");
+    return;
+  }
+
+  value_length = value_end - value_start;
+  while ((value_length >= 1) && ((value_start[value_length - 1] == ' ')
+          || (value_start[value_length - 1] == '\t')))
+    value_length--;
+
+  if (value_start[i] == '+' || value_start[i] == '-')
+    i++;
+  while (i < value_length) {
+    if (value_start[i] < '0' || value_start[i] > '9') {
+      GST_ERROR_OBJECT (qtdemux,
+          "error: incorrect value, integer was expected\n");
+      return;
+    }
+    i++;
+  }
+
+  if (value_length >= value_length_max || value_length < 1) {
+    GST_ERROR_OBJECT (qtdemux, "error: empty XML value or incorrect range\n");
+    return;
+  }
+
+  strncpy (init_view_ret, value_start, value_length_max);
+  init_view_ret[value_length] = '\0';
+
+  *value = strtol (init_view_ret, &endptr, 10);
+  if (endptr == init_view_ret) {
+    GST_ERROR_OBJECT (qtdemux, "error: no digits were found\n");
+    return;
+  }
+
+  return;
+}
+
+static void
+_get_string_value_from_xml_string (GstQTDemux * qtdemux,
+    const char *xml_str, const char *param_name, char **value)
+{
+  char *value_start, *value_end;
+  const short value_length_max = 256;
+  int value_length = 0;
+
+  value_start = (xml_str && param_name) ? strstr (xml_str, param_name) : NULL;
+
+  if (!value_start) {
+    GST_WARNING_OBJECT (qtdemux, "error: parameter does not exist: %s\n",
+        param_name);
+    return;
+  }
+
+  value_start += strlen (param_name);
+  while ((value_start[0] == ' ') || (value_start[0] == '\t'))
+    value_start++;
+
+  value_end = strchr (value_start, '<');
+  if (!value_end) {
+    GST_ERROR_OBJECT (qtdemux, "error: incorrect XML\n");
+    return;
+  }
+
+  value_length = value_end - value_start;
+  while ((value_length >= 1) && ((value_start[value_length - 1] == ' ')
+          || (value_start[value_length - 1] == '\t')))
+    value_length--;
+
+  if (value_length >= value_length_max || value_length < 1) {
+    GST_ERROR_OBJECT (qtdemux, "error: empty XML value or incorrect range\n");
+    return;
+  }
+
+  *value = strndup(value_start, value_length);
+
+  return;
+}
+
+static void
+_get_bool_value_from_xml_string (GstQTDemux * qtdemux,
+    const char *xml_str, const char *param_name, gboolean * value)
+{
+  char *value_start, *value_end;
+  int value_length = 0;
+
+  value_start = (xml_str && param_name) ? strstr (xml_str, param_name) : NULL;
+
+  if (!value_start) {
+    GST_WARNING_OBJECT (qtdemux, "error: parameter does not exist: %s\n",
+        param_name);
+    return;
+  }
+
+  value_start += strlen (param_name);
+  while ((value_start[0] == ' ') || (value_start[0] == '\t'))
+    value_start++;
+
+  value_end = strchr (value_start, '<');
+  if (!value_end) {
+    GST_ERROR_OBJECT (qtdemux, "error: incorrect XML\n");
+    return;
+  }
+
+  value_length = value_end - value_start;
+  while ((value_length >= 1) && ((value_start[value_length - 1] == ' ')
+          || (value_start[value_length - 1] == '\t')))
+    value_length--;
+
+  if (value_length < 1) {
+    GST_ERROR_OBJECT (qtdemux, "error: empty XML value or incorrect range\n");
+    return;
+  }
+
+  *value = g_strstr_len(value_start, value_length, "true") ? TRUE : FALSE;
+
+  return;
+}
+
+static void
+_parse_spatial_video_metadata_from_xml_string (GstQTDemux * qtdemux, const char *xmlStr)
+{
+  const char is_spherical_str[] = "<GSpherical:Spherical>";
+  const char is_stitched_str[] = "<GSpherical:Stitched>";
+  const char stitching_software_str[] = "<GSpherical:StitchingSoftware>";
+  const char projection_type_str[] = "<GSpherical:ProjectionType>";
+  const char stereo_mode_str[] = "<GSpherical:StereoMode>";
+  const char source_count_str[] = "<GSpherical:SourceCount>";
+  const char init_view_heading_str[] = "<GSpherical:InitialViewHeadingDegrees>";
+  const char init_view_pitch_str[] = "<GSpherical:InitialViewPitchDegrees>";
+  const char init_view_roll_str[] = "<GSpherical:InitialViewRollDegrees>";
+  const char timestamp_str[] = "<GSpherical:Timestamp>";
+  const char full_pano_width_str[] = "<GSpherical:FullPanoWidthPixels>";
+  const char full_pano_height_str[] = "<GSpherical:FullPanoHeightPixels>";
+  const char cropped_area_image_width_str[] =
+      "<GSpherical:CroppedAreaImageWidthPixels>";
+  const char cropped_area_image_height_str[] =
+      "<GSpherical:CroppedAreaImageHeightPixels>";
+  const char cropped_area_left_str[] = "<GSpherical:CroppedAreaLeftPixels>";
+  const char cropped_area_top_str[] = "<GSpherical:CroppedAreaTopPixels>";
+
+  QtDemuxSphericalMetadata * spherical_metadata = qtdemux->spherical_metadata;
+
+  _get_bool_value_from_xml_string (qtdemux, xmlStr, is_spherical_str,
+      (gboolean *) & spherical_metadata->is_spherical);
+  _get_bool_value_from_xml_string (qtdemux, xmlStr, is_stitched_str,
+      (gboolean *) & spherical_metadata->is_stitched);
+
+  if (spherical_metadata->is_spherical && spherical_metadata->is_stitched) {
+    _get_string_value_from_xml_string (qtdemux, xmlStr,
+        stitching_software_str, &spherical_metadata->stitching_software);
+    _get_string_value_from_xml_string (qtdemux, xmlStr,
+        projection_type_str, &spherical_metadata->projection_type);
+    _get_string_value_from_xml_string (qtdemux, xmlStr, stereo_mode_str,
+        &spherical_metadata->stereo_mode);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, source_count_str,
+        &spherical_metadata->source_count);
+    _get_int_value_from_xml_string (qtdemux, xmlStr,
+        init_view_heading_str, &spherical_metadata->init_view_heading);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, init_view_pitch_str,
+        &spherical_metadata->init_view_pitch);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, init_view_roll_str,
+        &spherical_metadata->init_view_roll);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, timestamp_str,
+        &spherical_metadata->timestamp);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, full_pano_width_str,
+        &spherical_metadata->full_pano_width_pixels);
+    _get_int_value_from_xml_string (qtdemux, xmlStr,
+        full_pano_height_str, &spherical_metadata->full_pano_height_pixels);
+    _get_int_value_from_xml_string (qtdemux, xmlStr,
+        cropped_area_image_width_str,
+        &spherical_metadata->cropped_area_image_width);
+    _get_int_value_from_xml_string (qtdemux, xmlStr,
+        cropped_area_image_height_str,
+        &spherical_metadata->cropped_area_image_height);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, cropped_area_left_str,
+        &spherical_metadata->cropped_area_left);
+    _get_int_value_from_xml_string (qtdemux, xmlStr, cropped_area_top_str,
+        &spherical_metadata->cropped_area_top);
+  }
+
+  return;
+}
+
+static void
+gst_tag_register_spherical_tags (void) {
+  gst_tag_register ("is_spherical", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-spherical"),
+      _("Flag indicating if the video is a spherical video"),
+      NULL);
+  gst_tag_register ("is_stitched", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-stitched"),
+      _("Flag indicating if the video is stitched"),
+      NULL);
+  gst_tag_register ("stitching_software", GST_TAG_FLAG_META,
+      G_TYPE_STRING,
+      _("tag-stitching-software"),
+      _("Software used to stitch the spherical video"),
+      NULL);
+  gst_tag_register ("projection_type", GST_TAG_FLAG_META,
+      G_TYPE_STRING,
+      _("tag-projection-type"),
+      _("Projection type used in the video frames"),
+      NULL);
+  gst_tag_register ("stereo_mode", GST_TAG_FLAG_META,
+      G_TYPE_STRING,
+      _("tag-stereo-mode"),
+      _("Description of stereoscopic 3D layout"),
+      NULL);
+  gst_tag_register ("source_count", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-source-count"),
+      _("Number of cameras used to create the spherical video"),
+      NULL);
+  gst_tag_register ("init_view_heading", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-init-view-heading"),
+      _("The heading angle of the initial view in degrees"),
+      NULL);
+  gst_tag_register ("init_view_pitch", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-init-view-pitch"),
+      _("The pitch angle of the initial view in degrees"),
+      NULL);
+  gst_tag_register ("init_view_roll", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-init-view-roll"),
+      _("The roll angle of the initial view in degrees"),
+      NULL);
+  gst_tag_register ("timestamp", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-timestamp"),
+      _("Epoch timestamp of when the first frame in the video was recorded"),
+      NULL);
+  gst_tag_register ("full_pano_width_pixels", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-full-pano-width"),
+      _("Width of the encoded video frame in pixels"),
+      NULL);
+  gst_tag_register ("full_pano_height_pixels", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-full-pano-height"),
+      _("Height of the encoded video frame in pixels"),
+      NULL);
+  gst_tag_register ("cropped_area_image_width", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-cropped-area-image-width"),
+      _("Width of the video frame to display (e.g. cropping)"),
+      NULL);
+  gst_tag_register ("cropped_area_image_height", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-cropped-area-image-height"),
+      _("Height of the video frame to display (e.g. cropping)"),
+      NULL);
+  gst_tag_register ("cropped_area_left", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-cropped-area-left"),
+      _("Column where the left edge of the image was cropped from the"
+          " full sized panorama"),
+      NULL);
+  gst_tag_register ("cropped_area_top", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-cropped-area-top"),
+      _("Row where the top edge of the image was cropped from the"
+          " full sized panorama"),
+      NULL);
+  gst_tag_register ("ambisonic_type", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-ambisonic-type"),
+      _("Specifies the type of ambisonic audio represented"),
+      NULL);
+  gst_tag_register ("ambisonic_format", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-ambisonic-format"),
+      _("Specifies the ambisonic audio format"),
+      NULL);
+  gst_tag_register ("ambisonic_order", GST_TAG_FLAG_META,
+      G_TYPE_INT,
+      _("tag-ambisonic-order"),
+      _("Specifies the ambisonic audio channel order"),
+      NULL);
+
+  return;
+}
+
+static void
+_send_spherical_metadata_msg_to_bus (GstQTDemux * qtdemux)
+{
+  GstTagList *taglist;
+  QtDemuxSphericalMetadata *spherical_metadata = qtdemux->spherical_metadata;
+
+  GST_DEBUG_OBJECT (qtdemux, "is_spherical = %d",
+      spherical_metadata->is_spherical);
+  GST_DEBUG_OBJECT (qtdemux, "is_stitched = %d",
+      spherical_metadata->is_stitched);
+  GST_DEBUG_OBJECT (qtdemux, "stitching_software = %s",
+      spherical_metadata->stitching_software);
+  GST_DEBUG_OBJECT (qtdemux, "projection_type = %s",
+      spherical_metadata->projection_type);
+  GST_DEBUG_OBJECT (qtdemux, "stereo_mode = %s",
+      spherical_metadata->stereo_mode);
+  GST_DEBUG_OBJECT (qtdemux, "source_count %d",
+      spherical_metadata->source_count);
+  GST_DEBUG_OBJECT (qtdemux, "init_view_heading = %d",
+      spherical_metadata->init_view_heading);
+  GST_DEBUG_OBJECT (qtdemux, "init_view_pitch = %d",
+      spherical_metadata->init_view_pitch);
+  GST_DEBUG_OBJECT (qtdemux, "init_view_roll = %d",
+      spherical_metadata->init_view_roll);
+  GST_DEBUG_OBJECT (qtdemux, "timestamp = %d", spherical_metadata->timestamp);
+  GST_DEBUG_OBJECT (qtdemux, "full_pano_width_pixels = %d",
+      spherical_metadata->full_pano_width_pixels);
+  GST_DEBUG_OBJECT (qtdemux, "full_pano_height_pixels = %d",
+      spherical_metadata->full_pano_height_pixels);
+  GST_DEBUG_OBJECT (qtdemux, "cropped_area_image_width = %d",
+      spherical_metadata->cropped_area_image_width);
+  GST_DEBUG_OBJECT (qtdemux, "cropped_area_image_height = %d",
+      spherical_metadata->cropped_area_image_height);
+  GST_DEBUG_OBJECT (qtdemux, "cropped_area_left = %d",
+      spherical_metadata->cropped_area_left);
+  GST_DEBUG_OBJECT (qtdemux, "cropped_area_top = %d",
+      spherical_metadata->cropped_area_top);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_type = %d",
+      spherical_metadata->ambisonic_type);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_order = %d",
+      spherical_metadata->ambisonic_order);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_format = %d",
+      spherical_metadata->ambisonic_format);
+
+  taglist = gst_tag_list_new_empty ();
+  gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
+      "is_spherical", spherical_metadata->is_spherical,
+      "is_stitched", spherical_metadata->is_stitched,
+      "source_count", spherical_metadata->source_count,
+      "init_view_heading", spherical_metadata->init_view_heading,
+      "init_view_pitch", spherical_metadata->init_view_pitch,
+      "init_view_roll", spherical_metadata->init_view_roll,
+      "timestamp", spherical_metadata->timestamp,
+      "full_pano_width_pixels", spherical_metadata->full_pano_width_pixels,
+      "full_pano_height_pixels", spherical_metadata->full_pano_height_pixels,
+      "cropped_area_image_width", spherical_metadata->cropped_area_image_width,
+      "cropped_area_image_height", spherical_metadata->cropped_area_image_height,
+      "cropped_area_left", spherical_metadata->cropped_area_left,
+      "cropped_area_top", spherical_metadata->cropped_area_top,
+      "ambisonic_type", spherical_metadata->ambisonic_type,
+      "ambisonic_format", spherical_metadata->ambisonic_format,
+      "ambisonic_order", spherical_metadata->ambisonic_order,
+      NULL);
+
+  if (spherical_metadata->stitching_software)
+    gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
+        "stitching_software", spherical_metadata->stitching_software,
+        NULL);
+  if (spherical_metadata->projection_type)
+    gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
+        "projection_type", spherical_metadata->projection_type,
+        NULL);
+  if (spherical_metadata->stereo_mode)
+    gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
+        "stereo_mode", spherical_metadata->stereo_mode,
+        NULL);
+
+  gst_element_post_message (GST_ELEMENT_CAST (qtdemux),
+          gst_message_new_tag (GST_OBJECT_CAST (qtdemux),
+                  gst_tag_list_copy (taglist)));
+
+  gst_tag_list_unref(taglist);
+
+  return;
+}
+
+static void
+qtdemux_parse_SA3D (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
+{
+  guint offset = 0;
+
+  guint8 version = 0;
+  guint8 ambisonic_type  = 0;
+  guint32 ambisonic_order = 0;
+  guint8 ambisonic_channel_ordering = 0;
+  guint8 ambisonic_normalization = 0;
+  guint32 num_channels = 0;
+  guint32 channel_map[49] = { 0 };      /* Up to 6th order */
+
+  int i;
+
+  GST_DEBUG_OBJECT (qtdemux, "qtdemux_parse_SA3D");
+
+  qtdemux->header_size += length;
+  offset = (QT_UINT32 (buffer) == 0) ? 16 : 8;
+
+  if (length <= offset + 16) {
+    GST_DEBUG_OBJECT (qtdemux, "SA3D atom is too short, skipping");
+    return;
+  }
+
+  version = QT_UINT8 (buffer + offset);
+  ambisonic_type = QT_UINT8 (buffer + offset + 1);
+  ambisonic_order = QT_UINT32 (buffer + offset + 2);
+  ambisonic_channel_ordering = QT_UINT8 (buffer + offset + 6);
+  ambisonic_normalization = QT_UINT8 (buffer + offset + 7);
+  num_channels = QT_UINT32 (buffer + offset + 8);
+  for (i = 0; i < num_channels; ++i)
+    channel_map[i] = QT_UINT32 (buffer + offset + 12 + i * 4);
+
+  GST_DEBUG_OBJECT (qtdemux, "version: %d", version);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_type: %d", ambisonic_type);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_order: %d", ambisonic_order);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_channel_ordering: %d",
+      ambisonic_channel_ordering);
+  GST_DEBUG_OBJECT (qtdemux, "ambisonic_normalization: %d",
+      ambisonic_normalization);
+  GST_DEBUG_OBJECT (qtdemux, "num_channels: %d", num_channels);
+  for (i = 0; i < num_channels; ++i)
+    GST_DEBUG_OBJECT (qtdemux, "channel_map: %d", channel_map[i]);
+
+  if (version == RFC_AMBISONIC_SA3DBOX_VERSION_SUPPORTED) {
+    if (ambisonic_type == RFC_AMBISONIC_TYPE_PERIPHONIC)
+      qtdemux->spherical_metadata->ambisonic_type = QTDEMUX_AMBISONIC_TYPE_PERIPHONIC;
+
+    if (ambisonic_order == RFC_AMBISONIC_ORDER_FOA) {
+      if (num_channels == 4) {
+        qtdemux->spherical_metadata->ambisonic_order = QTDEMUX_AMBISONIC_ORDER_FOA;
+
+        if ((ambisonic_channel_ordering == RFC_AMBISONIC_CHANNEL_ORDERING_ACN)
+            && (ambisonic_normalization == RFC_AMBISONIC_NORMALIZATION_SN3D)
+            && (channel_map[0] == 0) && (channel_map[1] == 1)
+            && (channel_map[2] == 2) && (channel_map[3] == 3))
+          qtdemux->spherical_metadata->ambisonic_format = QTDEMUX_AMBISONIC_FORMAT_AMBIX;
+
+        if ((ambisonic_channel_ordering == RFC_AMBISONIC_CHANNEL_ORDERING_FUMA)
+            && (ambisonic_normalization == RFC_AMBISONIC_NORMALIZATION_FUMA)
+            && (channel_map[0] == 0) && (channel_map[1] == 3)
+            && (channel_map[2] == 1) && (channel_map[3] == 2))
+          qtdemux->spherical_metadata->ambisonic_format = QTDEMUX_AMBISONIC_FORMAT_AMB;
+      }
+    }
+  }
+
+  return;
+}
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
 static void
 qtdemux_update_default_sample_cenc_settings (GstQTDemux * qtdemux,
     QtDemuxCencSampleSetInfo * info, guint32 is_encrypted,
@@ -2931,6 +3483,13 @@ qtdemux_parse_uuid (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
     0xa2, 0x44, 0x6c, 0x42, 0x7c, 0x64, 0x8d, 0xf4
   };
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+  static const guint8 spherical_uuid[] = {
+    0xff, 0xcc, 0x82, 0x63, 0xf8, 0x55, 0x4a, 0x93,
+    0x88, 0x14, 0x58, 0x7a, 0x02, 0x52, 0x1f, 0xdd
+  };
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
   guint offset;
 
   /* counts as header data */
@@ -2943,6 +3502,21 @@ qtdemux_parse_uuid (GstQTDemux * qtdemux, const guint8 * buffer, gint length)
     return;
   }
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+  if (memcmp (buffer + offset, spherical_uuid, 16) == 0) {
+    const char *contents;
+
+    GST_DEBUG_OBJECT (qtdemux, "spherical uuid was found");
+    contents = (char *) (buffer + offset + 16);
+    GST_DEBUG_OBJECT (qtdemux, "contents: %s\n", contents);
+
+    if (qtdemux->spherical_metadata)
+      _parse_spatial_video_metadata_from_xml_string (qtdemux, contents);
+
+    return;
+  }
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
+
   if (memcmp (buffer + offset, xmp_uuid, 16) == 0) {
     GstBuffer *buf;
     GstTagList *taglist;
@@ -4654,6 +5228,10 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux)
 beach:
   if (ret == GST_FLOW_EOS && (qtdemux->got_moov || qtdemux->media_caps)) {
     /* digested all data, show what we have */
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+    if (qtdemux->spherical_metadata)
+      _send_spherical_metadata_msg_to_bus (qtdemux);
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
     qtdemux_prepare_streams (qtdemux);
     QTDEMUX_EXPOSE_LOCK (qtdemux);
     ret = qtdemux_expose_streams (qtdemux);
@@ -8259,6 +8837,13 @@ qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, const guint8 * buffer,
         qtdemux_parse_container (qtdemux, node, buffer + 36, end);
         break;
       }
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+      case FOURCC_SA3D:
+      {
+        qtdemux_parse_SA3D (qtdemux, buffer, end - buffer);
+        break;
+      }
+#endif /* TIZEN_FEATURE_QTDEMUX_MODIFICATION */
       default:
         if (!strcmp (type->name, "unknown"))
           GST_MEMDUMP ("Unknown tag", buffer + 4, end - buffer - 4);
@@ -8601,9 +9186,16 @@ gst_qtdemux_guess_framerate (GstQTDemux * qtdemux, QtDemuxStream * stream)
           GST_TIME_FORMAT, duration, first_duration,
           n_samples - 1, GST_TIME_ARGS (avg_duration));
 
+#ifdef TIZEN_FEATURE_QTDEMUX_MODIFICATION
+      gst_video_guess_framerate (avg_duration,
+        &CUR_STREAM (stream)->fps_n, &CUR_STREAM (stream)->fps_d);
+      if (CUR_STREAM (stream)->fps_d == 0)
+        fps_available = FALSE;
+#else
       fps_available =
           gst_video_guess_framerate (avg_duration,
           &CUR_STREAM (stream)->fps_n, &CUR_STREAM (stream)->fps_d);
+#endif
 
       GST_DEBUG_OBJECT (qtdemux,
           "Calculating framerate, timescale %u gave fps_n %d fps_d %d",
@@ -10864,6 +11456,22 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
   stream = _create_stream (qtdemux, track_id);
   stream->stream_tags = gst_tag_list_make_writable (stream->stream_tags);
 
+#ifdef TIZEN_FEATURE_QTDEMUX_DURATION
+  if (!gst_byte_reader_skip (&tkhd, 4))
+    goto corrupt_file;
+
+  if (tkhd_version == 1) {
+    if (!gst_byte_reader_get_uint64_be (&tkhd, &stream->tkhd_duration))
+      goto corrupt_file;
+  } else {
+    guint32 dur = 0;
+    if (!gst_byte_reader_get_uint32_be (&tkhd, &dur))
+      goto corrupt_file;
+    stream->tkhd_duration = dur;
+  }
+  GST_INFO_OBJECT (qtdemux, "tkhd duration: %" G_GUINT64_FORMAT,
+      stream->tkhd_duration);
+#endif
   /* need defaults for fragments */
   qtdemux_parse_trex (qtdemux, stream, &dummy, &dummy, &dummy);
 
@@ -10988,7 +11596,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
     guint32 matrix[9];
 
     /* version 1 uses some 64-bit ints */
+#ifdef TIZEN_FEATURE_QTDEMUX_DURATION
+    if (!gst_byte_reader_skip (&tkhd, 16))
+#else
     if (!gst_byte_reader_skip (&tkhd, 20 + value_size))
+#endif
       goto corrupt_file;
 
     if (!qtdemux_parse_transformation_matrix (qtdemux, &tkhd, matrix, "tkhd"))
@@ -13260,6 +13872,9 @@ static GstFlowReturn
 qtdemux_prepare_streams (GstQTDemux * qtdemux)
 {
   GstFlowReturn ret = GST_FLOW_OK;
+#ifdef TIZEN_FEATURE_QTDEMUX_DURATION
+  guint64 tkhd_max_duration = 0;
+#endif
   gint i;
 
   GST_DEBUG_OBJECT (qtdemux, "prepare %u streams", QTDEMUX_N_STREAMS (qtdemux));
@@ -13281,6 +13896,10 @@ qtdemux_prepare_streams (GstQTDemux * qtdemux)
     } else {
       /* discard any stray moof */
       qtdemux->moof_offset = 0;
+#ifdef TIZEN_FEATURE_QTDEMUX_DURATION
+      if (tkhd_max_duration < stream->tkhd_duration)
+        tkhd_max_duration = stream->tkhd_duration;
+#endif
     }
 
     /* prepare braking */
@@ -13312,6 +13931,15 @@ qtdemux_prepare_streams (GstQTDemux * qtdemux)
     }
   }
 
+#ifdef TIZEN_FEATURE_QTDEMUX_DURATION
+  if (!qtdemux->fragmented && (qtdemux->duration > tkhd_max_duration)) {
+    GST_INFO_OBJECT (qtdemux,
+        "Update duration: %" G_GUINT64_FORMAT " -> %" G_GUINT64_FORMAT,
+        qtdemux->duration, tkhd_max_duration);
+    qtdemux->duration = tkhd_max_duration;
+  }
+#endif
+
   return ret;
 }