Commit message: qtdemux: Fix out of bounds read in tag parsing code

We can't simply assume that the length of the tag value as given
inside the stream is correct but should also check against the amount of
data we have actually available.

https://bugzilla.gnome.org/show_bug.cgi?id=775451
CVE: CVE-2016-10199
CWE: CWE-125
Vulnerability description: The qtdemux_tag_add_str_full function in gst/isomp4/qtdemux.c in gst-plugins-good in GStreamer before 1.10.3 allows remote attackers to cause a denial of service (out-of-bounds read and crash) via a crafted tag value.

Project: gst-plugins-good
File: qtdemux.c

qtdemux_parse_node (GstQTDemux * qtdemux, GNode * node, const guint8 * buffer,
    guint length)
{
  guint32 fourcc = 0;
  guint32 node_length = 0;
  const QtNodeType *type;
  const guint8 *end;

  GST_LOG_OBJECT (qtdemux, "qtdemux_parse buffer %p length %u", buffer, length);

  if (G_UNLIKELY (length < 8))
    goto not_enough_data;

  node_length = QT_UINT32 (buffer);
  fourcc = QT_FOURCC (buffer + 4);

  /* ignore empty nodes */
  if (G_UNLIKELY (fourcc == 0 || node_length == 8))
    return TRUE;

  type = qtdemux_type_get (fourcc);

  end = buffer + length;

  GST_LOG_OBJECT (qtdemux,
      "parsing '%" GST_FOURCC_FORMAT "', length=%u, name '%s'",
      GST_FOURCC_ARGS (fourcc), node_length, type->name);

  if (node_length > length)
    goto broken_atom_size;

  if (type->flags & QT_FLAG_CONTAINER) {
    qtdemux_parse_container (qtdemux, node, buffer + 8, end);
  } else {
    switch (fourcc) {
      case FOURCC_stsd:
      {
        if (node_length < 20) {
          GST_LOG_OBJECT (qtdemux, "skipping small stsd box");
          break;
        }
        GST_DEBUG_OBJECT (qtdemux,
            "parsing stsd (sample table, sample description) atom");
        /* Skip over 8 byte atom hdr + 1 byte version, 3 bytes flags, 4 byte num_entries */
        qtdemux_parse_container (qtdemux, node, buffer + 16, end);
        break;
      }
      case FOURCC_mp4a:
      case FOURCC_alac:
      case FOURCC_fLaC:
      {
        guint32 version;
        guint32 offset;
        guint min_size;

        /* also read alac (or whatever) in stead of mp4a in the following,
         * since a similar layout is used in other cases as well */
        if (fourcc == FOURCC_mp4a)
          min_size = 20;
        else if (fourcc == FOURCC_fLaC)
          min_size = 86;
        else
          min_size = 40;

        /* There are two things we might encounter here: a true mp4a atom, and
           an mp4a entry in an stsd atom. The latter is what we're interested
           in, and it looks like an atom, but isn't really one. The true mp4a
           atom is short, so we detect it based on length here. */
        if (length < min_size) {
          GST_LOG_OBJECT (qtdemux, "skipping small %" GST_FOURCC_FORMAT " box",
              GST_FOURCC_ARGS (fourcc));
          break;
        }

        /* 'version' here is the sound sample description version. Types 0 and
           1 are documented in the QTFF reference, but type 2 is not: it's
           described in Apple header files instead (struct SoundDescriptionV2
           in Movies.h) */
        version = QT_UINT16 (buffer + 16);

        GST_DEBUG_OBJECT (qtdemux, "%" GST_FOURCC_FORMAT " version 0x%08x",
            GST_FOURCC_ARGS (fourcc), version);

        /* parse any esds descriptors */
        switch (version) {
          case 0:
            offset = 0x24;
            break;
          case 1:
            offset = 0x34;
            break;
          case 2:
            offset = 0x48;
            break;
          default:
            GST_WARNING_OBJECT (qtdemux,
                "unhandled %" GST_FOURCC_FORMAT " version 0x%08x",
                GST_FOURCC_ARGS (fourcc), version);
            offset = 0;
            break;
        }
        if (offset)
          qtdemux_parse_container (qtdemux, node, buffer + offset, end);
        break;
      }
      case FOURCC_mp4v:
      case FOURCC_MP4V:
      case FOURCC_fmp4:
      case FOURCC_FMP4:
      case FOURCC_apcs:
      case FOURCC_apch:
      case FOURCC_apcn:
      case FOURCC_apco:
      case FOURCC_ap4h:
      {
        const guint8 *buf;
        guint32 version;
        int tlen;

        /* codec_data is contained inside these atoms, which all have
         * the same format. */

        GST_DEBUG_OBJECT (qtdemux, "parsing in %" GST_FOURCC_FORMAT,
            GST_FOURCC_ARGS (fourcc));
        version = QT_UINT32 (buffer + 16);
        GST_DEBUG_OBJECT (qtdemux, "version %08x", version);
        if (1 || version == 0x00000000) {
          buf = buffer + 0x32;

          /* FIXME Quicktime uses PASCAL string while
           * the iso format uses C strings. Check the file
           * type before attempting to parse the string here. */
          tlen = QT_UINT8 (buf);
          GST_DEBUG_OBJECT (qtdemux, "tlen = %d", tlen);
          buf++;
          GST_DEBUG_OBJECT (qtdemux, "string = %.*s", tlen, (char *) buf);
          /* the string has a reserved space of 32 bytes so skip
           * the remaining 31 */
          buf += 31;
          buf += 4;             /* and 4 bytes reserved */

          GST_MEMDUMP_OBJECT (qtdemux, "mp4v", buf, end - buf);

          qtdemux_parse_container (qtdemux, node, buf, end);
        }
        break;
      }
      case FOURCC_H264:
      {
        GST_MEMDUMP_OBJECT (qtdemux, "H264", buffer, end - buffer);
        qtdemux_parse_container (qtdemux, node, buffer + 0x56, end);
        break;
      }
      case FOURCC_avc1:
      {
        GST_MEMDUMP_OBJECT (qtdemux, "avc1", buffer, end - buffer);
        qtdemux_parse_container (qtdemux, node, buffer + 0x56, end);
        break;
      }
      case FOURCC_avc3:
      {
        GST_MEMDUMP_OBJECT (qtdemux, "avc3", buffer, end - buffer);
        qtdemux_parse_container (qtdemux, node, buffer + 0x56, end);
        break;
      }
      case FOURCC_H265:
      {
        GST_MEMDUMP_OBJECT (qtdemux, "H265", buffer, end - buffer);
        qtdemux_parse_container (qtdemux, node, buffer + 0x56, end);
        break;
      }
      case FOURCC_hvc1:
      {
        GST_MEMDUMP_OBJECT (qtdemux, "hvc1", buffer, end - buffer);
        qtdemux_parse_container (qtdemux, node, buffer + 0x56, end);
        break;
      }
      case FOURCC_hev1:
      {
        GST_MEMDUMP_OBJECT (qtdemux, "hev1", buffer, end - buffer);
        qtdemux_parse_container (qtdemux, node, buffer + 0x56, end);
        break;
      }
      case FOURCC_mjp2:
      {
        qtdemux_parse_container (qtdemux, node, buffer + 86, end);
        break;
      }
      case FOURCC_meta:
      {
        GST_DEBUG_OBJECT (qtdemux, "parsing meta atom");
        qtdemux_parse_container (qtdemux, node, buffer + 12, end);
        break;
      }
      case FOURCC_mp4s:
      {
        GST_MEMDUMP_OBJECT (qtdemux, "mp4s", buffer, end - buffer);
        /* Skip 8 byte header, plus 8 byte version + flags + entry_count */
        qtdemux_parse_container (qtdemux, node, buffer + 16, end);
        break;
      }
      case FOURCC_XiTh:
      {
        guint32 version;
        guint32 offset;

        version = QT_UINT32 (buffer + 12);
        GST_DEBUG_OBJECT (qtdemux, "parsing XiTh atom version 0x%08x", version);

        switch (version) {
          case 0x00000001:
            offset = 0x62;
            break;
          default:
            GST_DEBUG_OBJECT (qtdemux, "unknown version 0x%08x", version);
            offset = 0;
            break;
        }
        if (offset)
          qtdemux_parse_container (qtdemux, node, buffer + offset, end);
        break;
      }
      case FOURCC_in24:
      {
        qtdemux_parse_container (qtdemux, node, buffer + 0x34, end);
        break;
      }
      case FOURCC_uuid:
      {
        qtdemux_parse_uuid (qtdemux, buffer, end - buffer);
        break;
      }
      case FOURCC_encv:
      {
        qtdemux_parse_container (qtdemux, node, buffer + 86, end);
        break;
      }
      case FOURCC_enca:
      {
        qtdemux_parse_container (qtdemux, node, buffer + 36, end);
        break;
      }
      default:
        if (!strcmp (type->name, "unknown"))
          GST_MEMDUMP ("Unknown tag", buffer + 4, end - buffer - 4);
        break;
    }
  }
  GST_LOG_OBJECT (qtdemux, "parsed '%" GST_FOURCC_FORMAT "'",
      GST_FOURCC_ARGS (fourcc));
  return TRUE;

/* ERRORS */
not_enough_data:
  {
    GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX,
        (_("This file is corrupt and cannot be played.")),
        ("Not enough data for an atom header, got only %u bytes", length));
    return FALSE;
  }
broken_atom_size:
  {
    GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX,
        (_("This file is corrupt and cannot be played.")),
        ("Atom '%" GST_FOURCC_FORMAT "' has size of %u bytes, but we have only "
            "%u bytes available.", GST_FOURCC_ARGS (fourcc), node_length,
            length));
    return FALSE;
  }
}

Project: gst-plugins-good
File: qtdemux.c

qtdemux_process_redirects (GstQTDemux * qtdemux, GList * references)
{
  GstQtReference *best;
  GstStructure *s;
  GstMessage *msg;
  GValue list_val = { 0, };
  GList *l;

  g_assert (references != NULL);

  references = g_list_sort (references, qtdemux_redirects_sort_func);

  best = (GstQtReference *) references->data;

  g_value_init (&list_val, GST_TYPE_LIST);

  for (l = references; l != NULL; l = l->next) {
    GstQtReference *ref = (GstQtReference *) l->data;
    GValue struct_val = { 0, };

    ref->structure = gst_structure_new ("redirect",
        "new-location", G_TYPE_STRING, ref->location, NULL);

    if (ref->min_req_bitrate > 0) {
      gst_structure_set (ref->structure, "minimum-bitrate", G_TYPE_INT,
          ref->min_req_bitrate, NULL);
    }

    g_value_init (&struct_val, GST_TYPE_STRUCTURE);
    g_value_set_boxed (&struct_val, ref->structure);
    gst_value_list_append_value (&list_val, &struct_val);
    g_value_unset (&struct_val);
    /* don't free anything here yet, since we need best->structure below */
  }

  g_assert (best != NULL);
  s = gst_structure_copy (best->structure);

  if (g_list_length (references) > 1) {
    gst_structure_set_value (s, "locations", &list_val);
  }

  g_value_unset (&list_val);

  for (l = references; l != NULL; l = l->next) {
    GstQtReference *ref = (GstQtReference *) l->data;

    gst_structure_free (ref->structure);
    g_free (ref->location);
    g_free (ref);
  }
  g_list_free (references);

  GST_INFO_OBJECT (qtdemux, "posting redirect message: %" GST_PTR_FORMAT, s);
  msg = gst_message_new_element (GST_OBJECT_CAST (qtdemux), s);
  gst_element_post_message (GST_ELEMENT_CAST (qtdemux), msg);
  qtdemux->posted_redirect = TRUE;
}

Project: gst-plugins-good
File: qtdemux.c

qtdemux_sink_activate (GstPad * sinkpad, GstObject * parent)
{
  GstQuery *query;
  gboolean pull_mode;

  query = gst_query_new_scheduling ();

  if (!gst_pad_peer_query (sinkpad, query)) {
    gst_query_unref (query);
    goto activate_push;
  }

  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)
    goto activate_push;

  GST_DEBUG_OBJECT (sinkpad, "activating pull");
  return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE);

activate_push:
  {
    GST_DEBUG_OBJECT (sinkpad, "activating push");
    return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE);
  }
}

Project: gst-plugins-good
File: qtdemux.c

qtdemux_parse_tfhd (GstQTDemux * qtdemux, GstByteReader * tfhd,
    QtDemuxStream ** stream, guint32 * default_sample_duration,
    guint32 * default_sample_size, guint32 * default_sample_flags,
    gint64 * base_offset)
{
  guint32 flags = 0;
  guint32 track_id = 0;

  if (!gst_byte_reader_skip (tfhd, 1) ||
      !gst_byte_reader_get_uint24_be (tfhd, &flags))
    goto invalid_track;

  if (!gst_byte_reader_get_uint32_be (tfhd, &track_id))
    goto invalid_track;

  *stream = qtdemux_find_stream (qtdemux, track_id);
  if (G_UNLIKELY (!*stream))
    goto unknown_stream;

  if (flags & TF_DEFAULT_BASE_IS_MOOF)
    *base_offset = qtdemux->moof_offset;

  if (flags & TF_BASE_DATA_OFFSET)
    if (!gst_byte_reader_get_uint64_be (tfhd, (guint64 *) base_offset))
      goto invalid_track;

  /* obtain stream defaults */
  qtdemux_parse_trex (qtdemux, *stream,
      default_sample_duration, default_sample_size, default_sample_flags);

  /* FIXME: Handle TF_SAMPLE_DESCRIPTION_INDEX properly */
  if (flags & TF_SAMPLE_DESCRIPTION_INDEX)
    if (!gst_byte_reader_skip (tfhd, 4))
      goto invalid_track;

  if (flags & TF_DEFAULT_SAMPLE_DURATION)
    if (!gst_byte_reader_get_uint32_be (tfhd, default_sample_duration))
      goto invalid_track;

  if (flags & TF_DEFAULT_SAMPLE_SIZE)
    if (!gst_byte_reader_get_uint32_be (tfhd, default_sample_size))
      goto invalid_track;

  if (flags & TF_DEFAULT_SAMPLE_FLAGS)
    if (!gst_byte_reader_get_uint32_be (tfhd, default_sample_flags))
      goto invalid_track;

  return TRUE;

invalid_track:
  {
    GST_WARNING_OBJECT (qtdemux, "invalid track fragment header");
    return FALSE;
  }
unknown_stream:
  {
    GST_DEBUG_OBJECT (qtdemux, "unknown stream in tfhd");
    return TRUE;
  }
}

Project: gst-plugins-good
File: qtdemux.c

qtdemux_handle_xmp_taglist (GstQTDemux * qtdemux, GstTagList * taglist,
    GstTagList * xmptaglist)
{
  /* Strip out bogus fields */
  if (xmptaglist) {
    if (gst_tag_list_get_scope (taglist) == GST_TAG_SCOPE_GLOBAL) {
      gst_tag_list_remove_tag (xmptaglist, GST_TAG_VIDEO_CODEC);
      gst_tag_list_remove_tag (xmptaglist, GST_TAG_AUDIO_CODEC);
    } else {
      gst_tag_list_remove_tag (xmptaglist, GST_TAG_CONTAINER_FORMAT);
    }

    GST_DEBUG_OBJECT (qtdemux, "Found XMP tags %" GST_PTR_FORMAT, xmptaglist);

    /* prioritize native tags using _KEEP mode */
    gst_tag_list_insert (taglist, xmptaglist, GST_TAG_MERGE_KEEP);
    gst_tag_list_unref (xmptaglist);
  }
}

Project: gst-plugins-good
File: qtdemux.c

gst_qtdemux_append_protection_system_id (GstQTDemux * qtdemux,
    const gchar * system_id)
{
  gint i;

  if (!qtdemux->protection_system_ids)
    qtdemux->protection_system_ids =
        g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);
  /* Check whether we already have an entry for this system ID. */
  for (i = 0; i < qtdemux->protection_system_ids->len; ++i) {
    const gchar *id = g_ptr_array_index (qtdemux->protection_system_ids, i);
    if (g_ascii_strcasecmp (system_id, id) == 0) {
      return;
    }
  }
  GST_DEBUG_OBJECT (qtdemux, "Adding cenc protection system ID %s", system_id);
  g_ptr_array_add (qtdemux->protection_system_ids, g_ascii_strdown (system_id,
          -1));
}

Project: gst-plugins-good
File: qtdemux.c

qtdemux_parse_redirects (GstQTDemux * qtdemux)
{
  GNode *rmra, *rmda, *rdrf;

  rmra = qtdemux_tree_get_child_by_type (qtdemux->moov_node, FOURCC_rmra);
  if (rmra) {
    GList *redirects = NULL;

    rmda = qtdemux_tree_get_child_by_type (rmra, FOURCC_rmda);
    while (rmda) {
      GstQtReference ref = { NULL, NULL, 0, 0 };
      GNode *rmdr, *rmvc;

      if ((rmdr = qtdemux_tree_get_child_by_type (rmda, FOURCC_rmdr))) {
        ref.min_req_bitrate = QT_UINT32 ((guint8 *) rmdr->data + 12);
        GST_LOG_OBJECT (qtdemux, "data rate atom, required bitrate = %u",
            ref.min_req_bitrate);
      }

      if ((rmvc = qtdemux_tree_get_child_by_type (rmda, FOURCC_rmvc))) {
        guint32 package = QT_FOURCC ((guint8 *) rmvc->data + 12);
        guint version = QT_UINT32 ((guint8 *) rmvc->data + 16);

#ifndef GST_DISABLE_GST_DEBUG
        guint bitmask = QT_UINT32 ((guint8 *) rmvc->data + 20);
#endif
        guint check_type = QT_UINT16 ((guint8 *) rmvc->data + 24);

        GST_LOG_OBJECT (qtdemux,
            "version check atom [%" GST_FOURCC_FORMAT "], version=0x%08x"
            ", mask=%08x, check_type=%u", GST_FOURCC_ARGS (package), version,
            bitmask, check_type);
        if (package == FOURCC_qtim && check_type == 0) {
          ref.min_req_qt_version = version;
        }
      }

      rdrf = qtdemux_tree_get_child_by_type (rmda, FOURCC_rdrf);
      if (rdrf) {
        guint32 ref_type;
        guint8 *ref_data;
        guint ref_len;

        ref_len = QT_UINT32 ((guint8 *) rdrf->data);
        if (ref_len > 20) {
          ref_type = QT_FOURCC ((guint8 *) rdrf->data + 12);
          ref_data = (guint8 *) rdrf->data + 20;
          if (ref_type == FOURCC_alis) {
            guint record_len, record_version, fn_len;

            if (ref_len > 70) {
              /* MacOSX alias record, google for alias-layout.txt */
              record_len = QT_UINT16 (ref_data + 4);
              record_version = QT_UINT16 (ref_data + 4 + 2);
              fn_len = QT_UINT8 (ref_data + 50);
              if (record_len > 50 && record_version == 2 && fn_len > 0) {
                ref.location = g_strndup ((gchar *) ref_data + 51, fn_len);
              }
            } else {
              GST_WARNING_OBJECT (qtdemux, "Invalid rdrf/alis size (%u < 70)",
                  ref_len);
            }
          } else if (ref_type == FOURCC_url_) {
            ref.location = g_strndup ((gchar *) ref_data, ref_len - 8);
          } else {
            GST_DEBUG_OBJECT (qtdemux,
                "unknown rdrf reference type %" GST_FOURCC_FORMAT,
                GST_FOURCC_ARGS (ref_type));
          }
          if (ref.location != NULL) {
            GST_INFO_OBJECT (qtdemux, "New location: %s", ref.location);
            redirects =
                g_list_prepend (redirects, g_memdup (&ref, sizeof (ref)));
          } else {
            GST_WARNING_OBJECT (qtdemux,
                "Failed to extract redirect location from rdrf atom");
          }
        } else {
          GST_WARNING_OBJECT (qtdemux, "Invalid rdrf size (%u < 20)", ref_len);
        }
      }

      /* look for others */
      rmda = qtdemux_tree_get_sibling_by_type (rmda, FOURCC_rmda);
    }

    if (redirects != NULL) {
      qtdemux_process_redirects (qtdemux, redirects);
    }
  }
  return TRUE;
}

Project: gst-plugins-good
File: qtdemux.c

qtdemux_find_atom (GstQTDemux * qtdemux, guint64 * offset,
    guint64 * length, guint32 fourcc)
{
  GstFlowReturn ret;
  guint32 lfourcc;
  GstBuffer *buf;

  GST_LOG_OBJECT (qtdemux, "finding fourcc %" GST_FOURCC_FORMAT " at offset %"
      G_GUINT64_FORMAT, GST_FOURCC_ARGS (fourcc), *offset);

  while (TRUE) {
    GstMapInfo map;

    buf = NULL;
    ret = gst_pad_pull_range (qtdemux->sinkpad, *offset, 16, &buf);
    if (G_UNLIKELY (ret != GST_FLOW_OK))
      goto locate_failed;
    if (G_UNLIKELY (gst_buffer_get_size (buf) != 16)) {
      /* likely EOF */
      ret = GST_FLOW_EOS;
      gst_buffer_unref (buf);
      goto locate_failed;
    }
    gst_buffer_map (buf, &map, GST_MAP_READ);
    extract_initial_length_and_fourcc (map.data, 16, length, &lfourcc);
    gst_buffer_unmap (buf, &map);
    gst_buffer_unref (buf);

    if (G_UNLIKELY (*length == 0)) {
      GST_DEBUG_OBJECT (qtdemux, "invalid length 0");
      ret = GST_FLOW_ERROR;
      goto locate_failed;
    }

    if (lfourcc == fourcc) {
      GST_DEBUG_OBJECT (qtdemux, "found fourcc at offset %" G_GUINT64_FORMAT,
          *offset);
      break;
    } else {
      GST_LOG_OBJECT (qtdemux,
          "skipping atom '%" GST_FOURCC_FORMAT "' at %" G_GUINT64_FORMAT,
          GST_FOURCC_ARGS (fourcc), *offset);
      *offset += *length;
    }
  }

  return GST_FLOW_OK;

locate_failed:
  {
    /* might simply have had last one */
    GST_DEBUG_OBJECT (qtdemux, "fourcc not found");
    return ret;
  }
}

Project: gst-plugins-good
File: qtdemux.c

check_update_duration (GstQTDemux * qtdemux, GstClockTime duration)
{
  guint i;
  guint64 movdur;
  GstClockTime prevdur;

  movdur = GSTTIME_TO_QTTIME (qtdemux, duration);

  if (movdur > qtdemux->duration) {
    prevdur = QTTIME_TO_GSTTIME (qtdemux, qtdemux->duration);
    GST_DEBUG_OBJECT (qtdemux,
        "Updating total duration to %" GST_TIME_FORMAT " was %" GST_TIME_FORMAT,
        GST_TIME_ARGS (duration), GST_TIME_ARGS (prevdur));
    qtdemux->duration = movdur;
    GST_DEBUG_OBJECT (qtdemux,
        "qtdemux->segment.duration: %" GST_TIME_FORMAT " .stop: %"
        GST_TIME_FORMAT, GST_TIME_ARGS (qtdemux->segment.duration),
        GST_TIME_ARGS (qtdemux->segment.stop));
    if (qtdemux->segment.duration == prevdur) {
      /* If the current segment has duration/stop identical to previous duration
       * update them also (because they were set at that point in time with
       * the wrong duration */
      /* We convert the value *from* the timescale version to avoid rounding errors */
      GstClockTime fixeddur = QTTIME_TO_GSTTIME (qtdemux, movdur);
      GST_DEBUG_OBJECT (qtdemux, "Updated segment.duration and segment.stop");
      qtdemux->segment.duration = fixeddur;
      qtdemux->segment.stop = fixeddur;
    }
  }
  for (i = 0; i < qtdemux->n_streams; i++) {
    QtDemuxStream *stream = qtdemux->streams[i];
    if (stream) {
      movdur = GSTTIME_TO_QTSTREAMTIME (stream, duration);
      if (movdur > stream->duration) {
        GST_DEBUG_OBJECT (qtdemux,
            "Updating stream #%d duration to %" GST_TIME_FORMAT, i,
            GST_TIME_ARGS (duration));
        stream->duration = movdur;
        if (stream->dummy_segment) {
          /* Update all dummy values to new duration */
          stream->segments[0].stop_time = duration;
          stream->segments[0].duration = duration;
          stream->segments[0].media_stop = duration;

          /* let downstream know we possibly have a new stop time */
          if (stream->segment_index != -1) {
            GstClockTime pos;

            if (qtdemux->segment.rate >= 0) {
              pos = stream->segment.start;
            } else {
              pos = stream->segment.stop;
            }

            gst_qtdemux_stream_update_segment (qtdemux, stream,
                stream->segment_index, pos, NULL, NULL);
          }
        }
      }
    }
  }
}

Project: gst-plugins-good
File: qtdemux.c

gst_qtdemux_configure_stream (GstQTDemux * qtdemux, QtDemuxStream * stream)
{
  if (stream->subtype == FOURCC_vide) {
    /* fps is calculated base on the duration of the average framerate since
     * qt does not have a fixed framerate. */
    gboolean fps_available = TRUE;

    if ((stream->n_samples == 1) && (stream->first_duration == 0)) {
      /* still frame */
      stream->fps_n = 0;
      stream->fps_d = 1;
    } else {
      if (stream->duration == 0 || stream->n_samples < 2) {
        stream->fps_n = stream->timescale;
        stream->fps_d = 1;
        fps_available = FALSE;
      } else {
        GstClockTime avg_duration;
        guint64 duration;
        guint32 n_samples;

        /* duration and n_samples can be updated for fragmented format
         * so, framerate of fragmented format is calculated using data in a moof */
        if (qtdemux->fragmented && stream->n_samples_moof > 0
            && stream->duration_moof > 0) {
          n_samples = stream->n_samples_moof;
          duration = stream->duration_moof;
        } else {
          n_samples = stream->n_samples;
          duration = stream->duration;
        }

        /* Calculate a framerate, ignoring the first sample which is sometimes truncated */
        /* stream->duration is guint64, timescale, n_samples are guint32 */
        avg_duration =
            gst_util_uint64_scale_round (duration -
            stream->first_duration, GST_SECOND,
            (guint64) (stream->timescale) * (n_samples - 1));

        GST_LOG_OBJECT (qtdemux,
            "Calculating avg sample duration based on stream (or moof) duration %"
            G_GUINT64_FORMAT
            " minus first sample %u, leaving %d samples gives %"
            GST_TIME_FORMAT, duration, stream->first_duration,
            n_samples - 1, GST_TIME_ARGS (avg_duration));

        gst_video_guess_framerate (avg_duration, &stream->fps_n,
            &stream->fps_d);

        GST_DEBUG_OBJECT (qtdemux,
            "Calculating framerate, timescale %u gave fps_n %d fps_d %d",
            stream->timescale, stream->fps_n, stream->fps_d);
      }
    }

    if (stream->caps) {
      stream->caps = gst_caps_make_writable (stream->caps);

      gst_caps_set_simple (stream->caps,
          "width", G_TYPE_INT, stream->width,
          "height", G_TYPE_INT, stream->height, NULL);

      /* set framerate if calculated framerate is reliable */
      if (fps_available) {
        gst_caps_set_simple (stream->caps,
            "framerate", GST_TYPE_FRACTION, stream->fps_n, stream->fps_d, NULL);
      }

      /* calculate pixel-aspect-ratio using display width and height */
      GST_DEBUG_OBJECT (qtdemux,
          "video size %dx%d, target display size %dx%d", stream->width,
          stream->height, stream->display_width, stream->display_height);
      /* qt file might have pasp atom */
      if (stream->par_w > 0 && stream->par_h > 0) {
        GST_DEBUG_OBJECT (qtdemux, "par %d:%d", stream->par_w, stream->par_h);
        gst_caps_set_simple (stream->caps, "pixel-aspect-ratio",
            GST_TYPE_FRACTION, stream->par_w, stream->par_h, NULL);
      } else if (stream->display_width > 0 && stream->display_height > 0 &&
          stream->width > 0 && stream->height > 0) {
        gint n, d;

        /* calculate the pixel aspect ratio using the display and pixel w/h */
        n = stream->display_width * stream->height;
        d = stream->display_height * stream->width;
        if (n == d)
          n = d = 1;
        GST_DEBUG_OBJECT (qtdemux, "setting PAR to %d/%d", n, d);
        stream->par_w = n;
        stream->par_h = d;
        gst_caps_set_simple (stream->caps, "pixel-aspect-ratio",
            GST_TYPE_FRACTION, stream->par_w, stream->par_h, NULL);
      }

      if (stream->interlace_mode > 0) {
        if (stream->interlace_mode == 1) {
          gst_caps_set_simple (stream->caps, "interlace-mode", G_TYPE_STRING,
              "progressive", NULL);
        } else if (stream->interlace_mode == 2) {
          gst_caps_set_simple (stream->caps, "interlace-mode", G_TYPE_STRING,
              "interleaved", NULL);
          if (stream->field_order == 9) {
            gst_caps_set_simple (stream->caps, "field-order", G_TYPE_STRING,
                "top-field-first", NULL);
          } else if (stream->field_order == 14) {
            gst_caps_set_simple (stream->caps, "field-order", G_TYPE_STRING,
                "bottom-field-first", NULL);
          }
        }
      }

      /* Create incomplete colorimetry here if needed */
      if (stream->colorimetry.range ||
          stream->colorimetry.matrix ||
          stream->colorimetry.transfer || stream->colorimetry.primaries) {
        gchar *colorimetry =
            gst_video_colorimetry_to_string (&stream->colorimetry);
        gst_caps_set_simple (stream->caps, "colorimetry", G_TYPE_STRING,
            colorimetry, NULL);
        g_free (colorimetry);
      }

      if (stream->multiview_mode != GST_VIDEO_MULTIVIEW_MODE_NONE) {
        guint par_w = 1, par_h = 1;

        if (stream->par_w > 0 && stream->par_h > 0) {
          par_w = stream->par_w;
          par_h = stream->par_h;
        }

        if (gst_video_multiview_guess_half_aspect (stream->multiview_mode,
                stream->width, stream->height, par_w, par_h)) {
          stream->multiview_flags |= GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT;
        }

        gst_caps_set_simple (stream->caps,
            "multiview-mode", G_TYPE_STRING,
            gst_video_multiview_mode_to_caps_string (stream->multiview_mode),
            "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
            stream->multiview_flags, GST_FLAG_SET_MASK_EXACT, NULL);
      }
    }
  }

  else if (stream->subtype == FOURCC_soun) {
    if (stream->caps) {
      stream->caps = gst_caps_make_writable (stream->caps);
      if (stream->rate > 0)
        gst_caps_set_simple (stream->caps,
            "rate", G_TYPE_INT, (int) stream->rate, NULL);
      if (stream->n_channels > 0)
        gst_caps_set_simple (stream->caps,
            "channels", G_TYPE_INT, stream->n_channels, NULL);
      if (stream->n_channels > 2) {
        /* FIXME: Need to parse the 'chan' atom to get channel layouts
         * correctly; this is just the minimum we can do - assume
         * we don't actually have any channel positions. */
        gst_caps_set_simple (stream->caps,
            "channel-mask", GST_TYPE_BITMASK, G_GUINT64_CONSTANT (0), NULL);
      }
    }
  }

  if (stream->pad) {
    GstCaps *prev_caps = NULL;

    GST_PAD_ELEMENT_PRIVATE (stream->pad) = stream;
    gst_pad_set_event_function (stream->pad, gst_qtdemux_handle_src_event);
    gst_pad_set_query_function (stream->pad, gst_qtdemux_handle_src_query);
    gst_pad_set_active (stream->pad, TRUE);

    gst_pad_use_fixed_caps (stream->pad);

    if (stream->protected) {
      if (!gst_qtdemux_configure_protected_caps (qtdemux, stream)) {
        GST_ERROR_OBJECT (qtdemux,
            "Failed to configure protected stream caps.");
        return FALSE;
      }
    }

    if (stream->new_stream) {
      gchar *stream_id;
      GstEvent *event;
      GstStreamFlags stream_flags;

      event =
          gst_pad_get_sticky_event (qtdemux->sinkpad, GST_EVENT_STREAM_START,
          0);
      if (event) {
        if (gst_event_parse_group_id (event, &qtdemux->group_id))
          qtdemux->have_group_id = TRUE;
        else
          qtdemux->have_group_id = FALSE;
        gst_event_unref (event);
      } else if (!qtdemux->have_group_id) {
        qtdemux->have_group_id = TRUE;
        qtdemux->group_id = gst_util_group_id_next ();
      }

      stream->new_stream = FALSE;
      stream_id =
          gst_pad_create_stream_id_printf (stream->pad,
          GST_ELEMENT_CAST (qtdemux), "%03u", stream->track_id);
      event = gst_event_new_stream_start (stream_id);
      if (qtdemux->have_group_id)
        gst_event_set_group_id (event, qtdemux->group_id);
      stream_flags = GST_STREAM_FLAG_NONE;
      if (stream->disabled)
        stream_flags |= GST_STREAM_FLAG_UNSELECT;
      if (stream->sparse)
        stream_flags |= GST_STREAM_FLAG_SPARSE;
      gst_event_set_stream_flags (event, stream_flags);
      gst_pad_push_event (stream->pad, event);
      g_free (stream_id);
    }

    prev_caps = gst_pad_get_current_caps (stream->pad);

    if (!prev_caps || !gst_caps_is_equal_fixed (prev_caps, stream->caps)) {
      GST_DEBUG_OBJECT (qtdemux, "setting caps %" GST_PTR_FORMAT, stream->caps);
      gst_pad_set_caps (stream->pad, stream->caps);
    } else {
      GST_DEBUG_OBJECT (qtdemux, "ignore duplicated caps");
    }

    if (prev_caps)
      gst_caps_unref (prev_caps);
    stream->new_caps = FALSE;
  }
  return TRUE;
}

Project: gst-plugins-good
File: qtdemux.c

qtdemux_parse_amr_bitrate (GstBuffer * buf, gboolean wb)
{
  /* The 'damr' atom is of the form:
   *
   * | vendor | decoder_ver | mode_set | mode_change_period | frames/sample |
   *    32 b       8 b          16 b           8 b                 8 b
   *
   * The highest set bit of the first 7 (AMR-NB) or 8 (AMR-WB) bits of mode_set
   * represents the highest mode used in the stream (and thus the maximum
   * bitrate), with a couple of special cases as seen below.
   */

  /* Map of frame type ID -> bitrate */
  static const guint nb_bitrates[] = {
    4750, 5150, 5900, 6700, 7400, 7950, 10200, 12200
  };
  static const guint wb_bitrates[] = {
    6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850
  };
  GstMapInfo map;
  gsize max_mode;
  guint16 mode_set;

  gst_buffer_map (buf, &map, GST_MAP_READ);

  if (map.size != 0x11) {
    GST_DEBUG ("Atom should have size 0x11, not %" G_GSIZE_FORMAT, map.size);
    goto bad_data;
  }

  if (QT_FOURCC (map.data + 4) != FOURCC_damr) {
    GST_DEBUG ("Unknown atom in %" GST_FOURCC_FORMAT,
        GST_FOURCC_ARGS (QT_UINT32 (map.data + 4)));
    goto bad_data;
  }

  mode_set = QT_UINT16 (map.data + 13);

  if (mode_set == (wb ? AMR_WB_ALL_MODES : AMR_NB_ALL_MODES))
    max_mode = 7 + (wb ? 1 : 0);
  else
    /* AMR-NB modes fo from 0-7, and AMR-WB modes go from 0-8 */
    max_mode = g_bit_nth_msf ((gulong) mode_set & (wb ? 0x1ff : 0xff), -1);

  if (max_mode == -1) {
    GST_DEBUG ("No mode indication was found (mode set) = %x",
        (guint) mode_set);
    goto bad_data;
  }

  gst_buffer_unmap (buf, &map);
  return wb ? wb_bitrates[max_mode] : nb_bitrates[max_mode];

bad_data:
  gst_buffer_unmap (buf, &map);
  return 0;
}

Project: gst-plugins-good
File: qtdemux.c

gst_qtdemux_push_event (GstQTDemux * qtdemux, GstEvent * event)
{
  guint n;
  gboolean has_valid_stream = FALSE;
  GstEventType etype = GST_EVENT_TYPE (event);

  GST_DEBUG_OBJECT (qtdemux, "pushing %s event on all source pads",
      GST_EVENT_TYPE_NAME (event));

  for (n = 0; n < qtdemux->n_streams; n++) {
    GstPad *pad;
    QtDemuxStream *stream = qtdemux->streams[n];
    GST_DEBUG_OBJECT (qtdemux, "pushing on pad %i", n);

    if ((pad = stream->pad)) {
      has_valid_stream = TRUE;

      if (etype == GST_EVENT_EOS) {
        /* let's not send twice */
        if (stream->sent_eos)
          continue;
        stream->sent_eos = TRUE;
      }

      gst_pad_push_event (pad, gst_event_ref (event));
    }
  }

  gst_event_unref (event);

  /* if it is EOS and there are no pads, post an error */
  if (!has_valid_stream && etype == GST_EVENT_EOS) {
    gst_qtdemux_post_no_playable_stream_error (qtdemux);
  }
}

Project: gst-plugins-good
File: qtdemux.c

next_entry_size (GstQTDemux * demux)
{
  QtDemuxStream *stream;
  int i;
  int smallidx = -1;
  guint64 smalloffs = (guint64) - 1;
  QtDemuxSample *sample;

  GST_LOG_OBJECT (demux, "Finding entry at offset %" G_GUINT64_FORMAT,
      demux->offset);

  for (i = 0; i < demux->n_streams; i++) {
    stream = demux->streams[i];

    if (stream->sample_index == -1) {
      stream->sample_index = 0;
      stream->offset_in_sample = 0;
    }

    if (stream->sample_index >= stream->n_samples) {
      GST_LOG_OBJECT (demux, "stream %d samples exhausted", i);
      continue;
    }

    if (!qtdemux_parse_samples (demux, stream, stream->sample_index)) {
      GST_LOG_OBJECT (demux, "Parsing of index %u from stbl atom failed!",
          stream->sample_index);
      return -1;
    }

    sample = &stream->samples[stream->sample_index];

    GST_LOG_OBJECT (demux,
        "Checking Stream %d (sample_index:%d / offset:%" G_GUINT64_FORMAT
        " / size:%" G_GUINT32_FORMAT ")", i, stream->sample_index,
        sample->offset, sample->size);

    if (((smalloffs == -1)
            || (sample->offset < smalloffs)) && (sample->size)) {
      smallidx = i;
      smalloffs = sample->offset;
    }
  }

  GST_LOG_OBJECT (demux,
      "stream %d offset %" G_GUINT64_FORMAT " demux->offset :%"
      G_GUINT64_FORMAT, smallidx, smalloffs, demux->offset);

  if (smallidx == -1)
    return -1;

  stream = demux->streams[smallidx];
  sample = &stream->samples[stream->sample_index];

  if (sample->offset >= demux->offset) {
    demux->todrop = sample->offset - demux->offset;
    return sample->size + demux->todrop;
  }

  GST_DEBUG_OBJECT (demux,
      "There wasn't any entry at offset %" G_GUINT64_FORMAT, demux->offset);
  return -1;
}

Project: gst-plugins-good
File: qtdemux.c

qtdemux_parse_theora_extension (GstQTDemux * qtdemux, QtDemuxStream * stream,
    GNode * xdxt)
{
  int len = QT_UINT32 (xdxt->data);
  guint8 *buf = xdxt->data;
  guint8 *end = buf + len;
  GstBuffer *buffer;

  /* skip size and type */
  buf += 8;
  end -= 8;

  while (buf < end) {
    gint size;
    guint32 type;

    size = QT_UINT32 (buf);
    type = QT_FOURCC (buf + 4);

    GST_LOG_OBJECT (qtdemux, "%p %p", buf, end);

    if (buf + size > end || size <= 0)
      break;

    buf += 8;
    size -= 8;

    GST_WARNING_OBJECT (qtdemux, "have cookie %" GST_FOURCC_FORMAT,
        GST_FOURCC_ARGS (type));

    switch (type) {
      case FOURCC_tCtH:
        buffer = gst_buffer_new_and_alloc (size);
        gst_buffer_fill (buffer, 0, buf, size);
        stream->buffers = g_slist_append (stream->buffers, buffer);
        GST_LOG_OBJECT (qtdemux, "parsing theora header");
        break;
      case FOURCC_tCt_:
        buffer = gst_buffer_new_and_alloc (size);
        gst_buffer_fill (buffer, 0, buf, size);
        stream->buffers = g_slist_append (stream->buffers, buffer);
        GST_LOG_OBJECT (qtdemux, "parsing theora comment");
        break;
      case FOURCC_tCtC:
        buffer = gst_buffer_new_and_alloc (size);
        gst_buffer_fill (buffer, 0, buf, size);
        stream->buffers = g_slist_append (stream->buffers, buffer);
        GST_LOG_OBJECT (qtdemux, "parsing theora codebook");
        break;
      default:
        GST_WARNING_OBJECT (qtdemux,
            "unknown theora cookie %" GST_FOURCC_FORMAT,
            GST_FOURCC_ARGS (type));
        break;
    }
    buf += size;
  }
  return TRUE;
}

Project: gst-plugins-good
File: qtdemux.c

qtdemux_tag_add_num (GstQTDemux * qtdemux, GstTagList * taglist,
    const char *tag1, const char *tag2, GNode * node)
{
  GNode *data;
  int len;
  int type;
  int n1, n2;

  data = qtdemux_tree_get_child_by_type (node, FOURCC_data);
  if (data) {
    len = QT_UINT32 (data->data);
    type = QT_UINT32 ((guint8 *) data->data + 8);
    if (type == 0x00000000 && len >= 22) {
      n1 = QT_UINT16 ((guint8 *) data->data + 18);
      n2 = QT_UINT16 ((guint8 *) data->data + 20);
      if (n1 > 0) {
        GST_DEBUG_OBJECT (qtdemux, "adding tag %s=%d", tag1, n1);
        gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag1, n1, NULL);
      }
      if (n2 > 0) {
        GST_DEBUG_OBJECT (qtdemux, "adding tag %s=%d", tag2, n2);
        gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag2, n2, NULL);
      }
    }
  }
}

Project: gst-plugins-good
File: qtdemux.c

qtdemux_generic_caps (GstQTDemux * qtdemux, QtDemuxStream * stream,
    guint32 fourcc, const guint8 * stsd_data, gchar ** codec_name)
{
  GstCaps *caps;

  switch (fourcc) {
    case FOURCC_m1v:
      _codec ("MPEG 1 video");
      caps = gst_caps_new_simple ("video/mpeg", "mpegversion", G_TYPE_INT, 1,
          "systemstream", G_TYPE_BOOLEAN, FALSE, NULL);
      break;
    default:
      caps = NULL;
      break;
  }
  return caps;
}

Project: gst-plugins-good
File: qtdemux.c

qtdemux_tag_add_year (GstQTDemux * qtdemux, GstTagList * taglist,
    const char *tag, const char *dummy, GNode * node)
{
  guint16 y;
  GDate *date;
  gint len;

  len = QT_UINT32 (node->data);
  if (len < 14)
    return;

  y = QT_UINT16 ((guint8 *) node->data + 12);
  if (y == 0) {
    GST_DEBUG_OBJECT (qtdemux, "year: %u is not a valid year", y);
    return;
  }
  GST_DEBUG_OBJECT (qtdemux, "year: %u", y);

  date = g_date_new_dmy (1, 1, y);
  gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, date, NULL);
  g_date_free (date);
}

Project: gst-plugins-good
File: qtdemux.c

qtdemux_tag_add_keywords (GstQTDemux * qtdemux, GstTagList * taglist,
    const char *tag, const char *dummy, GNode * node)
{
  const gchar *env_vars[] = { "GST_QT_TAG_ENCODING", "GST_TAG_ENCODING", NULL };
  guint8 *data;
  char *s, *t, *k = NULL;
  int len;
  int offset;
  int count;

  /* first try normal string tag if major brand not 3GP */
  if (!qtdemux_is_brand_3gp (qtdemux, TRUE)) {
    if (!qtdemux_tag_add_str_full (qtdemux, taglist, tag, dummy, node)) {
      /* hm, that did not work, maybe 3gpp storage in non-3gpp major brand;
       * let's try it 3gpp way after minor safety check */
      data = node->data;
      if (QT_UINT32 (data) < 15 || !qtdemux_is_brand_3gp (qtdemux, FALSE))
        return;
    } else
      return;
  }

  GST_DEBUG_OBJECT (qtdemux, "found 3gpp keyword tag");

  data = node->data;

  len = QT_UINT32 (data);
  if (len < 15)
    goto short_read;

  count = QT_UINT8 (data + 14);
  offset = 15;
  for (; count; count--) {
    gint slen;

    if (offset + 1 > len)
      goto short_read;
    slen = QT_UINT8 (data + offset);
    offset += 1;
    if (offset + slen > len)
      goto short_read;
    s = gst_tag_freeform_string_to_utf8 ((char *) node->data + offset,
        slen, env_vars);
    if (s) {
      GST_DEBUG_OBJECT (qtdemux, "adding keyword %s", GST_STR_NULL (s));
      if (k) {
        t = g_strjoin (",", k, s, NULL);
        g_free (s);
        g_free (k);
        k = t;
      } else {
        k = s;
      }
    } else {
      GST_DEBUG_OBJECT (qtdemux, "failed to convert keyword to UTF-8");
    }
    offset += slen;
  }

done:
  if (k) {
    GST_DEBUG_OBJECT (qtdemux, "adding tag %s", GST_STR_NULL (k));
    gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, tag, k, NULL);
  }
  g_free (k);

  return;

  /* ERRORS */
short_read:
  {
    GST_DEBUG_OBJECT (qtdemux, "short read parsing 3GP keywords");
    goto done;
  }
}

Project: gst-plugins-good
File: qtdemux.c

gst_qtdemux_seek_to_previous_keyframe (GstQTDemux * qtdemux)
{
  guint8 n = 0;
  guint32 seg_idx = 0, k_index = 0;
  guint32 ref_seg_idx, ref_k_index;
  GstClockTime k_pos = 0, last_stop = 0;
  QtDemuxSegment *seg = NULL;
  QtDemuxStream *ref_str = NULL;
  guint64 seg_media_start_mov;  /* segment media start time in mov format */
  guint64 target_ts;

  /* Now we choose an arbitrary stream, get the previous keyframe timestamp
   * and finally align all the other streams on that timestamp with their
   * respective keyframes */
  for (n = 0; n < qtdemux->n_streams; n++) {
    QtDemuxStream *str = qtdemux->streams[n];

    /* No candidate yet, take the first stream */
    if (!ref_str) {
      ref_str = str;
      continue;
    }

    /* So that stream has a segment, we prefer video streams */
    if (str->subtype == FOURCC_vide) {
      ref_str = str;
      break;
    }
  }

  if (G_UNLIKELY (!ref_str)) {
    GST_DEBUG_OBJECT (qtdemux, "couldn't find any stream");
    goto eos;
  }

  if (G_UNLIKELY (!ref_str->from_sample)) {
    GST_DEBUG_OBJECT (qtdemux, "reached the beginning of the file");
    goto eos;
  }

  /* So that stream has been playing from from_sample to to_sample. We will
   * get the timestamp of the previous sample and search for a keyframe before
   * that. For audio streams we do an arbitrary jump in the past (10 samples) */
  if (ref_str->subtype == FOURCC_vide) {
    k_index = gst_qtdemux_find_keyframe (qtdemux, ref_str,
        ref_str->from_sample - 1);
  } else {
    if (ref_str->from_sample >= 10)
      k_index = ref_str->from_sample - 10;
    else
      k_index = 0;
  }

  target_ts =
      ref_str->samples[k_index].timestamp +
      ref_str->samples[k_index].pts_offset;

  /* get current segment for that stream */
  seg = &ref_str->segments[ref_str->segment_index];
  /* Use segment start in original timescale for comparisons */
  seg_media_start_mov = seg->trak_media_start;

  GST_LOG_OBJECT (qtdemux, "keyframe index %u ts %" G_GUINT64_FORMAT
      " seg start %" G_GUINT64_FORMAT " %" GST_TIME_FORMAT "\n",
      k_index, target_ts, seg_media_start_mov,
      GST_TIME_ARGS (seg->media_start));

  /* Crawl back through segments to find the one containing this I frame */
  while (target_ts < seg_media_start_mov) {
    GST_DEBUG_OBJECT (qtdemux,
        "keyframe position (sample %u) is out of segment %u " " target %"
        G_GUINT64_FORMAT " seg start %" G_GUINT64_FORMAT, k_index,
        ref_str->segment_index, target_ts, seg_media_start_mov);

    if (G_UNLIKELY (!ref_str->segment_index)) {
      /* Reached first segment, let's consider it's EOS */
      goto eos;
    }
    ref_str->segment_index--;
    seg = &ref_str->segments[ref_str->segment_index];
    /* Use segment start in original timescale for comparisons */
    seg_media_start_mov = seg->trak_media_start;
  }
  /* Calculate time position of the keyframe and where we should stop */
  k_pos =
      QTSTREAMTIME_TO_GSTTIME (ref_str,
      target_ts - seg->trak_media_start) + seg->time;
  last_stop =
      QTSTREAMTIME_TO_GSTTIME (ref_str,
      ref_str->samples[ref_str->from_sample].timestamp -
      seg->trak_media_start) + seg->time;

  GST_DEBUG_OBJECT (qtdemux, "preferred stream played from sample %u, "
      "now going to sample %u (pts %" GST_TIME_FORMAT ")", ref_str->from_sample,
      k_index, GST_TIME_ARGS (k_pos));

  /* Set last_stop with the keyframe timestamp we pushed of that stream */
  qtdemux->segment.position = last_stop;
  GST_DEBUG_OBJECT (qtdemux, "last_stop now is %" GST_TIME_FORMAT,
      GST_TIME_ARGS (last_stop));

  if (G_UNLIKELY (last_stop < qtdemux->segment.start)) {
    GST_DEBUG_OBJECT (qtdemux, "reached the beginning of segment");
    goto eos;
  }

  ref_seg_idx = ref_str->segment_index;
  ref_k_index = k_index;

  /* Align them all on this */
  for (n = 0; n < qtdemux->n_streams; n++) {
    guint32 index = 0;
    GstClockTime seg_time = 0;
    QtDemuxStream *str = qtdemux->streams[n];

    /* aligning reference stream again might lead to backing up to yet another
     * keyframe (due to timestamp rounding issues),
     * potentially putting more load on downstream; so let's try to avoid */
    if (str == ref_str) {
      seg_idx = ref_seg_idx;
      seg = &str->segments[seg_idx];
      k_index = ref_k_index;
      GST_DEBUG_OBJECT (qtdemux, "reference stream %d segment %d, "
          "sample at index %d", n, ref_str->segment_index, k_index);
    } else {
      seg_idx = gst_qtdemux_find_segment (qtdemux, str, k_pos);
      GST_DEBUG_OBJECT (qtdemux,
          "stream %d align segment %d for keyframe pos %" GST_TIME_FORMAT, n,
          seg_idx, GST_TIME_ARGS (k_pos));

      /* get segment and time in the segment */
      seg = &str->segments[seg_idx];
      seg_time = k_pos - seg->time;

      /* get the media time in the segment.
       * No adjustment for empty "filler" segments */
      if (seg->media_start != GST_CLOCK_TIME_NONE)
        seg_time += seg->media_start;

      /* get the index of the sample with media time */
      index = gst_qtdemux_find_index_linear (qtdemux, str, seg_time);
      GST_DEBUG_OBJECT (qtdemux,
          "stream %d sample for %" GST_TIME_FORMAT " at %u", n,
          GST_TIME_ARGS (seg_time), index);

      /* find previous keyframe */
      k_index = gst_qtdemux_find_keyframe (qtdemux, str, index);
    }

    /* Remember until where we want to go */
    str->to_sample = str->from_sample - 1;
    /* Define our time position */
    target_ts =
        str->samples[k_index].timestamp + str->samples[k_index].pts_offset;
    str->time_position = QTSTREAMTIME_TO_GSTTIME (str, target_ts) + seg->time;
    if (seg->media_start != GST_CLOCK_TIME_NONE)
      str->time_position -= seg->media_start;

    /* Now seek back in time */
    gst_qtdemux_move_stream (qtdemux, str, k_index);
    GST_DEBUG_OBJECT (qtdemux, "stream %d keyframe at %u, time position %"
        GST_TIME_FORMAT " playing from sample %u to %u", n, k_index,
        GST_TIME_ARGS (str->time_position), str->from_sample, str->to_sample);
  }

  return GST_FLOW_OK;

eos:
  return GST_FLOW_EOS;
}

Project: gst-plugins-good
File: qtdemux.c

qtdemux_tag_add_blob (GNode * node, GstQtDemuxTagList * qtdemuxtaglist)
{
  gint len;
  guint8 *data;
  GstBuffer *buf;
  gchar *media_type;
  const gchar *style;
  GstSample *sample;
  GstStructure *s;
  guint i;
  guint8 ndata[4];
  GstQTDemux *demux = qtdemuxtaglist->demux;
  GstTagList *taglist = qtdemuxtaglist->taglist;

  data = node->data;
  len = QT_UINT32 (data);
  buf = gst_buffer_new_and_alloc (len);
  gst_buffer_fill (buf, 0, data, len);

  /* heuristic to determine style of tag */
  if (QT_FOURCC (data + 4) == FOURCC_____ ||
      (len > 8 + 12 && QT_FOURCC (data + 12) == FOURCC_data))
    style = "itunes";
  else if (demux->major_brand == FOURCC_qt__)
    style = "quicktime";
  /* fall back to assuming iso/3gp tag style */
  else
    style = "iso";

  /* santize the name for the caps. */
  for (i = 0; i < 4; i++) {
    guint8 d = data[4 + i];
    if (g_ascii_isalnum (d))
      ndata[i] = g_ascii_tolower (d);
    else
      ndata[i] = '_';
  }

  media_type = g_strdup_printf ("application/x-gst-qt-%c%c%c%c-tag",
      ndata[0], ndata[1], ndata[2], ndata[3]);
  GST_DEBUG_OBJECT (demux, "media type %s", media_type);

  s = gst_structure_new (media_type, "style", G_TYPE_STRING, style, NULL);
  sample = gst_sample_new (buf, NULL, NULL, s);
  gst_buffer_unref (buf);
  g_free (media_type);

  GST_DEBUG_OBJECT (demux, "adding private tag; size %d, info %" GST_PTR_FORMAT,
      len, s);

  gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND,
      GST_QT_DEMUX_PRIVATE_TAG, sample, NULL);

  gst_sample_unref (sample);
}

Project: gst-plugins-good
File: qtdemux.c

gst_qtdemux_change_state (GstElement * element, GstStateChange transition)
{
  GstQTDemux *qtdemux = GST_QTDEMUX (element);
  GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE;

  switch (transition) {
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      break;
    default:
      break;
  }

  result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);

  switch (transition) {
    case GST_STATE_CHANGE_PAUSED_TO_READY:{
      gst_qtdemux_reset (qtdemux, TRUE);
      break;
    }
    default:
      break;
  }

  return result;
}