adding new quicktime parser:
authorArtyom Baginski <artm@nerve.v2.nl>
Wed, 1 May 2002 18:41:59 +0000 (18:41 +0000)
committerArtyom Baginski <artm@nerve.v2.nl>
Wed, 1 May 2002 18:41:59 +0000 (18:41 +0000)
Original commit message from CVS:
adding new quicktime parser:
- openquicktime free (hense gst/qtdemux)
- no more seeks for parsing -> better for network streams
- uses GstByteStream
- less memcpy's
- long ChangeLog record in pompous style

common
configure.ac
gst/qtdemux/Makefile.am [new file with mode: 0644]
gst/qtdemux/README [new file with mode: 0644]
gst/qtdemux/qtdemux.c [new file with mode: 0644]
gst/qtdemux/qtdemux.h [new file with mode: 0644]

diff --git a/common b/common
index 745e216..e5997d9 160000 (submodule)
--- a/common
+++ b/common
@@ -1 +1 @@
-Subproject commit 745e216da016ee67bb498de56969c266ffa29d8c
+Subproject commit e5997d9e2b4e162ad423f9b9ec3ac9b29e12bb05
index dd83696..9f28cc1 100644 (file)
@@ -174,7 +174,7 @@ GST_PLUGINS_ALL="\
         cutter deinterlace flx goom intfloat law level\
         median mpeg1enc mpeg1sys mpeg1videoparse mpeg2enc mpeg2sub\
         mpegaudio mpegaudioparse mpegstream mpegtypes modplug\
-        monoscope passthrough playondemand rtjpeg silence sine\
+        monoscope passthrough playondemand qtdemux rtjpeg silence sine\
         smooth spectrum speed stereo stereomono\
         synaesthesia udp videoscale volenv volume vumeter wavparse y4m"
 
@@ -773,6 +773,7 @@ gst/modplug/libmodplug/Makefile
 gst/monoscope/Makefile
 gst/passthrough/Makefile
 gst/playondemand/Makefile
+gst/qtdemux/Makefile
 gst/rtjpeg/Makefile
 gst/silence/Makefile
 gst/sine/Makefile
diff --git a/gst/qtdemux/Makefile.am b/gst/qtdemux/Makefile.am
new file mode 100644 (file)
index 0000000..b6ba807
--- /dev/null
@@ -0,0 +1,13 @@
+plugindir = $(libdir)/gst
+
+plugin_LTLIBRARIES = libqtdemux.la
+
+libqtdemux_la_CFLAGS = ${GST_CFLAGS}
+libqtdemux_la_LIBADD = 
+libqtdemux_la_LDFLAGS = ${GST_PLUGIN_LDFLAGS} 
+
+libqtdemux_la_SOURCES = qtdemux.c
+
+noinst_HEADERS = qtdemux.h
+
+EXTRA_DIST = README
diff --git a/gst/qtdemux/README b/gst/qtdemux/README
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/gst/qtdemux/qtdemux.c b/gst/qtdemux/qtdemux.c
new file mode 100644 (file)
index 0000000..31c3f2d
--- /dev/null
@@ -0,0 +1,702 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include "qtdemux.h"
+
+/* elementfactory information */
+static GstElementDetails 
+gst_qtdemux_details = 
+{
+  "quicktime parser",
+  "Parser/Video",
+  "Parses a quicktime stream into audio and video substreams",
+  VERSION,
+  "A.Baguinski <artm@v2.nl>",
+  "(C) 2002",
+};
+
+static GstCaps* quicktime_type_find (GstBuffer *buf, gpointer private);
+
+/* typefactory for 'quicktime' */
+static GstTypeDefinition quicktimedefinition = {
+  "qtdemux_video/quicktime",
+  "video/quicktime",
+  ".mov",
+  quicktime_type_find,
+};
+
+enum {
+  LAST_SIGNAL
+};
+
+enum {
+  ARG_0
+};
+
+GST_PAD_TEMPLATE_FACTORY (sink_templ,
+  "sink",
+  GST_PAD_SINK,
+  GST_PAD_ALWAYS,
+  GST_CAPS_NEW (
+    "qtdemux_sink",
+     "video/quicktime",
+     NULL
+  )
+);
+
+/* 
+ * so far i only support Photo Jpeg videos and no audio. 
+ * after this one works ok, i'll see what's next.
+ */
+GST_PAD_TEMPLATE_FACTORY (src_video_templ,
+  "video_%02d",
+  GST_PAD_SRC,
+  GST_PAD_SOMETIMES,
+  GST_CAPS_NEW (
+    "qtdemux_src_video",
+    "video/jpeg",
+      "width", GST_PROPS_INT_RANGE (16, 4096),
+      "height", GST_PROPS_INT_RANGE (16, 4096)
+  )
+);
+
+static GstElementClass *parent_class = NULL;
+/* 
+ * contains correspondence between atom types and
+ * GstQtpAtomType structures 
+ */
+static GHashTable * gst_qtp_type_registry; 
+
+typedef struct {
+  guint32 type;
+  GstQtpAtomType atype;
+} GstQtpTypePair;
+
+static void gst_qtp_trak_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter);
+static void gst_qtp_tkhd_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter);
+static void gst_qtp_hdlr_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter);
+static void gst_qtp_stsd_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter);
+static void gst_qtp_stts_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter);
+static void gst_qtp_stsc_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter);
+static void gst_qtp_stsz_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter);
+static void gst_qtp_stco_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter);
+static void gst_qtp_mdhd_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter);
+static void gst_qtp_mdat_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter);
+
+GstQtpTypePair gst_qtp_type_table[] = {
+  { GST_MAKE_FOURCC('m','o','o','v'), {GST_QTP_CONTAINER_ATOM,NULL} },
+  { GST_MAKE_FOURCC('t','r','a','k'), {GST_QTP_CONTAINER_ATOM,gst_qtp_trak_handler} },
+  { GST_MAKE_FOURCC('e','d','t','s'), {GST_QTP_CONTAINER_ATOM,NULL} },
+  { GST_MAKE_FOURCC('m','d','i','a'), {GST_QTP_CONTAINER_ATOM,NULL} },
+  { GST_MAKE_FOURCC('m','i','n','f'), {GST_QTP_CONTAINER_ATOM,NULL} },
+  { GST_MAKE_FOURCC('d','i','n','f'), {GST_QTP_CONTAINER_ATOM,NULL} },
+  { GST_MAKE_FOURCC('s','t','b','l'), {GST_QTP_CONTAINER_ATOM,NULL} },
+  { GST_MAKE_FOURCC('m','d','a','t'), {0,gst_qtp_mdat_handler} },
+  { GST_MAKE_FOURCC('m','v','h','d'), {0,NULL} },
+  { GST_MAKE_FOURCC('t','k','h','d'), {0,gst_qtp_tkhd_handler} },
+  { GST_MAKE_FOURCC('e','l','s','t'), {0,NULL} },
+  { GST_MAKE_FOURCC('m','d','h','d'), {0,gst_qtp_mdhd_handler} },
+  { GST_MAKE_FOURCC('h','d','l','r'), {0,gst_qtp_hdlr_handler} },
+  { GST_MAKE_FOURCC('v','m','h','d'), {0,NULL} },
+  { GST_MAKE_FOURCC('d','r','e','f'), {0,NULL} },
+  { GST_MAKE_FOURCC('s','t','t','s'), {0,gst_qtp_stts_handler} },
+  { GST_MAKE_FOURCC('s','t','s','d'), {0,gst_qtp_stsd_handler} },
+  { GST_MAKE_FOURCC('s','t','s','z'), {0,gst_qtp_stsz_handler} },
+  { GST_MAKE_FOURCC('s','t','s','c'), {0,gst_qtp_stsc_handler} },
+  { GST_MAKE_FOURCC('s','t','c','o'), {0,gst_qtp_stco_handler} }
+};
+
+#define GST_QTP_TYPE_CNT sizeof(gst_qtp_type_table)/sizeof(GstQtpTypePair)
+
+static void gst_qtdemux_class_init (GstQTDemuxClass *klass);
+static void gst_qtdemux_init (GstQTDemux *quicktime_demux);
+static void gst_qtdemux_loop (GstElement *element);
+static GstElementStateReturn gst_qtdemux_change_state (GstElement * element);
+static gint gst_guint32_compare(gconstpointer _a, gconstpointer _b);
+
+static GType
+gst_qtdemux_get_type (void) 
+{
+  static GType qtdemux_type = 0;
+
+  if (!qtdemux_type) {
+    static const GTypeInfo qtdemux_info = {
+      sizeof(GstQTDemuxClass), NULL, NULL,
+      (GClassInitFunc)gst_qtdemux_class_init,
+      NULL, NULL, sizeof(GstQTDemux), 0,
+      (GInstanceInitFunc)gst_qtdemux_init,
+    };
+    qtdemux_type = g_type_register_static (GST_TYPE_ELEMENT, "GstQTDemux", &qtdemux_info, 0);
+  }
+  return qtdemux_type;
+}
+
+static void
+gst_qtdemux_class_init (GstQTDemuxClass *klass) 
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+  int i;
+
+  gobject_class = (GObjectClass*)klass;
+  gstelement_class = (GstElementClass*)klass;
+
+  parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
+
+  gstelement_class->change_state = gst_qtdemux_change_state;
+
+  gst_qtp_type_registry = g_hash_table_new(g_int_hash,g_int_equal);
+  for(i=0;i<GST_QTP_TYPE_CNT;i++) {
+    g_hash_table_insert(gst_qtp_type_registry,&(gst_qtp_type_table[i].type),&(gst_qtp_type_table[i].atype));
+  }
+}
+
+static GstElementStateReturn
+gst_qtdemux_change_state (GstElement * element)
+{
+  GstQTDemux * qtdemux = GST_QTDEMUX (element);
+
+  switch (GST_STATE_TRANSITION (element)) {
+    case GST_STATE_READY_TO_PAUSED:
+      qtdemux->bs = gst_bytestream_new (qtdemux->sinkpad);
+      break;
+    case GST_STATE_PAUSED_TO_READY:
+      gst_bytestream_destroy (qtdemux->bs);
+      break;
+    default:
+      break;
+  }
+  parent_class->change_state (element);
+  return GST_STATE_SUCCESS;
+}
+
+static void 
+gst_qtdemux_init (GstQTDemux *qtdemux) 
+{
+  guint i;
+               
+  qtdemux->sinkpad = gst_pad_new_from_template (GST_PAD_TEMPLATE_GET (sink_templ), "sink");
+  gst_element_set_loop_function (GST_ELEMENT (qtdemux), gst_qtdemux_loop);
+  gst_element_add_pad (GST_ELEMENT (qtdemux), qtdemux->sinkpad);
+
+  for (i=0; i<GST_QTDEMUX_MAX_VIDEO_PADS; i++) 
+         qtdemux->video_pad[i] = NULL;
+  qtdemux->num_video_pads = 0;
+
+  qtdemux->bs_pos = 0;
+  qtdemux->nested = NULL;
+  qtdemux->nested_cnt = 0;
+  qtdemux->tracks = NULL;
+  qtdemux->samples = NULL;
+}
+
+static GstCaps*
+quicktime_type_find (GstBuffer *buf,
+             gpointer private)
+{
+  gchar *data = GST_BUFFER_DATA (buf);
+
+  /* exactly like in the old version */
+  if (!strncmp (&data[4], "wide", 4) ||
+      !strncmp (&data[4], "moov", 4) ||
+      !strncmp (&data[4], "mdat", 4))  {
+    return gst_caps_new ("quicktime_type_find",
+                        "video/quicktime", 
+                        NULL);
+  }
+  return NULL;
+}
+
+static gboolean
+plugin_init (GModule *module, GstPlugin *plugin)
+{
+  GstElementFactory *factory;
+  GstTypeFactory *type;
+
+  if (!gst_library_load("gstbytestream")) {
+    gst_info("qtdemux: could not load support library 'gstbytestream'\n");
+    return FALSE;
+  }
+
+  factory = gst_element_factory_new("qtdemux",GST_TYPE_QTDEMUX,
+                                   &gst_qtdemux_details);
+  g_return_val_if_fail(factory != NULL, FALSE);
+
+  gst_element_factory_add_pad_template (factory, GST_PAD_TEMPLATE_GET (sink_templ));
+  gst_element_factory_add_pad_template (factory, GST_PAD_TEMPLATE_GET (src_video_templ));
+
+  type = gst_type_factory_new (&quicktimedefinition);
+  gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (type));
+
+  gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory));
+
+  return TRUE;
+}
+
+GstPluginDesc plugin_desc = {
+  GST_VERSION_MAJOR,
+  GST_VERSION_MINOR,
+  "qtdemux",
+  plugin_init
+};
+
+static gboolean
+gst_qtdemux_handle_event (GstQTDemux * qtdemux)
+{
+  guint32 remaining;
+  GstEvent * event;
+  GstEventType type;
+
+  gst_bytestream_get_status (qtdemux->bs,&remaining,&event);
+  type = event? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;
+
+  switch (type) {
+    case GST_EVENT_EOS:
+      gst_pad_event_default (qtdemux->sinkpad,event);
+      break;
+    case GST_EVENT_DISCONTINUOUS:
+      gst_bytestream_flush_fast (qtdemux->bs, remaining);
+    default:
+      gst_pad_event_default (qtdemux->sinkpad,event);
+      break;
+  }
+  return TRUE;
+}
+
+static gboolean gst_qtp_read_bytes_atom_head(GstQTDemux * qtdemux,GstQtpAtom * atom);
+static gboolean gst_qtp_skip(GstQTDemux * qtdemux, guint64 skip);
+static gboolean gst_qtp_skip_atom(GstQTDemux * qtdemux, GstQtpAtom * atom);
+static gboolean gst_qtp_skip_container(GstQTDemux * qtdemux, guint32 type);
+
+/* new track emerges here */
+static GstQtpTrack * track_to_be = NULL;
+
+/* 
+ * - gst_qtp_* functions together with gst_qtdemux_loop implement quicktime
+ * parser.
+ */
+
+static void
+gst_qtdemux_loop (GstElement *element)
+{
+  GstQTDemux * qtdemux = GST_QTDEMUX (element);
+  GstQtpAtom atom;
+  GstQtpAtomType * atom_type;
+
+  /* ain't we out of the current container? */
+  if (qtdemux->nested) {
+    GstQtpAtom * current = (GstQtpAtom *) qtdemux->nested->data;
+    while (current && (current->size!=0) && (current->start+current->size <= qtdemux->bs_pos)) {
+      /* indeed we are! */
+      qtdemux->nested = qtdemux->nested->next;
+      qtdemux->nested_cnt--;
+      /* if atom type has a handler call it with enter=FALSE (i.e. leave) */
+      atom_type = g_hash_table_lookup (gst_qtp_type_registry,&(current->type));
+      if (atom_type && atom_type->handler) 
+       atom_type->handler(qtdemux,current,FALSE);
+      free(current);
+      current = qtdemux->nested?(GstQtpAtom *) qtdemux->nested->data:NULL;
+    }
+  }
+
+  gst_qtp_read_bytes_atom_head(qtdemux,&atom);
+  GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtdemux_loop: atom(%c%c%c%c,%llu,%llu)\n",GST_FOURCC_TO_CHARSEQ(atom.type),atom.start,atom.size);
+
+  atom_type = g_hash_table_lookup (gst_qtp_type_registry,&atom.type);
+  if (!atom_type) {
+    gst_qtp_skip_atom(qtdemux,&atom);
+    return;
+  }
+
+  if (atom_type->flags & GST_QTP_CONTAINER_ATOM) {
+    GstQtpAtom * new_atom;
+    new_atom = malloc(sizeof(GstQtpAtom));
+    memcpy(new_atom,&atom,sizeof(GstQtpAtom));
+    qtdemux->nested_cnt++;
+    qtdemux->nested = g_slist_prepend (qtdemux->nested, new_atom);
+    if (atom_type->handler) 
+      atom_type->handler(qtdemux,&atom,TRUE);
+  } else {
+    /* leaf atom */
+    if (atom_type->handler)
+      atom_type->handler(qtdemux,&atom,TRUE);
+    /*
+     * if there wasn't a handler - we skip the whole atom
+     * if there was - ensure that next thing read will be after the atom
+     * (handler isn't obligated to read anything)
+     */
+    gst_qtp_skip_atom(qtdemux,&atom);
+    return;
+  }
+}
+
+/* 
+ * peeks an atom header,
+ * advances qtdemux->bs_pos (cause bytestream won't tell)
+ * flushes bytestream
+ */
+static gboolean
+gst_qtp_read_bytes_atom_head(GstQTDemux * qtdemux,GstQtpAtom * atom)
+{
+  GstByteStream * bs = qtdemux->bs;
+  GstQtpAtomMinHeader * amh = NULL;
+  guint64 * esize=NULL;
+
+  /* FIXME this can't be right, rewrite with _read */
+  do { /* do ... while (event()) is necessary for bytestream events */
+    if (!amh) {
+      if ((amh = (GstQtpAtomMinHeader*) gst_bytestream_peek_bytes (bs, 8)))  {
+       atom->size = GUINT32_FROM_BE(amh->size);
+       atom->type = amh->type; /* don't need to turn this around magicly FIXME this can depend on endiannes */
+       atom->start = qtdemux->bs_pos;
+       gst_bytestream_flush (bs, 8);
+       qtdemux->bs_pos += 8;
+      }
+    }
+    if (amh) {
+      if (atom->size == 1) { /* need to peek extended size field */
+       if ((esize = (guint64*) gst_bytestream_peek_bytes (bs, 8))) {
+         atom->size = GUINT64_FROM_BE(*esize);
+         gst_bytestream_flush (bs, 8);
+         qtdemux->bs_pos += 8;
+         return TRUE;
+       }
+      } else {
+       return TRUE;
+      }
+    }
+  } while (gst_qtdemux_handle_event (qtdemux));
+  return TRUE;
+}
+
+static void
+gst_qtp_read_bytes(GstQTDemux * qtdemux, void * buffer, size_t size)
+{
+  void * data;
+  GstByteStream * bs = qtdemux->bs;
+  do {
+    if ((data = gst_bytestream_peek_bytes (bs,size))) {
+      memcpy(buffer,data,size);
+      gst_bytestream_flush(bs,size);
+      qtdemux->bs_pos += size;
+      return;
+    }
+  } while (gst_qtdemux_handle_event (qtdemux));
+}
+
+static GstBuffer *
+gst_qtp_read(GstQTDemux * qtdemux, size_t size)
+{
+  GstBuffer * buf;
+  GstByteStream * bs = qtdemux->bs;
+  do {
+    if ((buf = gst_bytestream_read (bs,size))) {
+      qtdemux->bs_pos += size;
+      return buf;
+    }
+  } while (gst_qtdemux_handle_event (qtdemux));
+  return NULL;
+}
+
+/*
+ * skips some input (e.g. to ignore unknown atom)
+ */
+static gboolean
+gst_qtp_skip(GstQTDemux * qtdemux, guint64 skip)
+{
+  GstByteStream * bs = qtdemux->bs;
+  
+  if (skip) {
+    gst_bytestream_flush(bs,skip);
+    qtdemux->bs_pos += skip;
+  }
+  return TRUE;
+}
+
+/* convenience function for skipping the given atom */
+static gboolean
+gst_qtp_skip_atom(GstQTDemux * qtdemux, GstQtpAtom * atom)
+{
+  if (qtdemux->bs_pos < atom->start + atom->size) {
+    guint64 skip = atom->start + atom->size - qtdemux->bs_pos;
+    return gst_qtp_skip(qtdemux,skip);
+  } else 
+    return FALSE;
+}
+
+/* skips the container with type 'type' if finds it in the nesting stack */
+static gboolean
+gst_qtp_skip_container(GstQTDemux * qtdemux, guint32 type)
+{
+  GSList * iter = qtdemux->nested;
+
+  while (iter && ((GstQtpAtom*)(iter->data))->type != type)
+    iter = iter->next;
+
+  if (iter) 
+    return gst_qtp_skip_atom(qtdemux,(GstQtpAtom*)iter->data);
+  else
+    return FALSE;
+}
+
+static gint 
+gst_guint32_compare(gconstpointer a, gconstpointer b)
+{
+  if ((guint32*)a < (guint32*)b) 
+    return -1;
+  else if ((guint32*)a > (guint32*)b)
+    return 1;
+  else
+    return 0;
+}
+
+static void 
+gst_qtp_trak_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter)
+{
+  if (enter) { /* enter trak */
+    GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_trak_handler: enter\n");
+    track_to_be = malloc(sizeof(GstQtpTrack));
+    track_to_be->stsd = NULL;
+    track_to_be->stts = NULL;
+    track_to_be->stsc = NULL;
+    track_to_be->stsz = NULL;
+    track_to_be->stco = NULL;
+    track_to_be->samples = NULL;
+    track_to_be->pad = NULL;
+  } else { /* leave trak */
+    GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_trak_handler: leave\n");
+    if (track_to_be) { /* if we didnt discard this track earlier */
+      GstQtpStscRec * stsc;
+      guint32 * stsz, * stco, offset;
+      int chunk,sample,nchunks,nsamples,stsc_idx,nstsc;
+      GstCaps * newcaps = NULL;
+
+      /* process sample tables */
+
+      /*
+       * FIXME have to check which sample tables are present and which are not
+       * and skip the track if there's not enough tables or set default values
+       * if some optional tables are missing
+       */
+
+      /*
+       * FIXME i assume that there's only one of each stsd record and stts
+       * record in the tables that's not always true, this must be changed
+       * later, as soon as i encounter qt file with bigger tables.
+       */
+      track_to_be->format = ((GstQtpStsdRec*)GST_BUFFER_DATA(track_to_be->stsd))->format;
+      GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_trak_handler: format: %c%c%c%c\n",GST_FOURCC_TO_CHARSEQ(track_to_be->format));
+      track_to_be->sample_duration = GUINT32_FROM_BE(((GstQtpSttsRec*)GST_BUFFER_DATA(track_to_be->stts))->duration);
+      GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_trak_handler: sample duration: %d\n",track_to_be->sample_duration);
+      /* 
+       * depending on format we can decide to refuse this track all together 
+       * if we don't know what for format that it. 
+       */
+      switch (track_to_be->format) {
+       case GST_MAKE_FOURCC('j','p','e','g'):
+         track_to_be->pad = gst_pad_new_from_template(
+             GST_PAD_TEMPLATE_GET(src_video_templ),
+             g_strdup_printf("video_%02d",qtdemux->num_video_pads++));
+         newcaps = GST_CAPS_NEW(
+             "qtdemux_video_src",
+             "video/jpeg",
+             "width", GST_PROPS_INT(track_to_be->width),
+             "height", GST_PROPS_INT(track_to_be->height));
+         gst_pad_try_set_caps(track_to_be->pad,newcaps);
+         gst_element_add_pad(GST_ELEMENT(qtdemux),track_to_be->pad);
+         break;
+      }
+
+      /* 
+       * now let's find all about individual samples and put them into samples
+       * tree
+       */
+      if (!qtdemux->samples) { 
+       qtdemux->samples = g_tree_new(gst_guint32_compare);
+      }
+      stsc = (GstQtpStscRec*)GST_BUFFER_DATA(track_to_be->stsc);
+      stsz = (guint32*)GST_BUFFER_DATA(track_to_be->stsz);
+      stco = (guint32*)GST_BUFFER_DATA(track_to_be->stco);
+      nchunks = GST_BUFFER_SIZE(track_to_be->stco)/sizeof(guint32);
+      nsamples = GST_BUFFER_SIZE(track_to_be->stsz)/sizeof(guint32);
+      nstsc = GST_BUFFER_SIZE(track_to_be->stsc)/sizeof(GstQtpStscRec);
+
+      track_to_be->samples = malloc(nsamples*sizeof(GstQtpSample));
+      for(chunk=0,sample=0,stsc_idx=0;
+         chunk<nchunks;
+         chunk++) {
+       int i;
+       offset = GUINT32_FROM_BE(stco[chunk]);
+       if (stsc_idx+1<nstsc && chunk+1==GUINT32_FROM_BE(stsc[stsc_idx+1].first_chunk)) {
+         stsc_idx++;
+       }
+       for(i=0;i<GUINT32_FROM_BE(stsc[stsc_idx].samples_per_chunk);i++,sample++) {
+         guint32 size = GUINT32_FROM_BE(stsz[sample]);
+         track_to_be->samples[sample].offset = offset;
+         track_to_be->samples[sample].size = size;
+         track_to_be->samples[sample].timestamp = sample*((1000000*track_to_be->sample_duration)/track_to_be->time_scale);
+         track_to_be->samples[sample].track = track_to_be;
+         g_tree_insert(qtdemux->samples,&(track_to_be->samples[sample].offset),&(track_to_be->samples[sample]));
+         offset += size;
+       }
+      }
+
+      GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_trak_handler: trak added to the list\n");
+      qtdemux->tracks = g_list_prepend(qtdemux->tracks,track_to_be);
+
+      gst_buffer_unref(track_to_be->stsd);
+      gst_buffer_unref(track_to_be->stts);
+      gst_buffer_unref(track_to_be->stsc);
+      gst_buffer_unref(track_to_be->stsz);
+      gst_buffer_unref(track_to_be->stco);
+      track_to_be = 0;
+    }
+  }
+}
+
+/* 
+ * weird formats they apple guys are using 
+ * weird conversion copied from openquicktime 
+ * FIXME either it can be done more beautiful/fast way or this fixme has to go
+ */
+static float 
+fixed32_to_float(guint32 fixed)
+{
+  unsigned char * data = (unsigned char*)&fixed;
+  guint32 a, b, c, d;
+  a = data[0];
+  b = data[1];
+  c = data[2];
+  d = data[3];
+  a = (a << 8) + b;
+  b = (c << 8) + d;
+  return (float)a + (float)b / 65536;
+}
+
+static void 
+gst_qtp_tkhd_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter)
+{
+  guint32 wh[2];
+  /* if we get here track_to_be must be not NULL */
+  g_assert(track_to_be);
+
+  gst_qtp_skip(qtdemux,76); /* don't need those values */
+  gst_qtp_read_bytes(qtdemux,wh,8);
+  track_to_be->width = (guint32) fixed32_to_float(wh[0]);
+  track_to_be->height = (guint32) fixed32_to_float(wh[1]);
+  GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_tkhd_handler: track dimmensions: %dx%d\n",track_to_be->width,track_to_be->height);
+}
+
+static void
+gst_qtp_hdlr_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter)
+{
+  guint32 a[3];
+
+  gst_qtp_read_bytes(qtdemux,a,12);
+  GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_hdlr_handler: %c%c%c%c %c%c%c%c\n",GST_FOURCC_TO_CHARSEQ(a[1]),GST_FOURCC_TO_CHARSEQ(a[2]));
+  if (a[1]==GST_MAKE_FOURCC('m','h','l','r') && a[2]!=GST_MAKE_FOURCC('v','i','d','e')) {
+    GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_hdlr_handler: rejecting the track\n");
+    /* forget about this track! */
+    free(track_to_be);
+    track_to_be = NULL;
+    gst_qtp_skip_container(qtdemux,GST_MAKE_FOURCC('t','r','a','k'));
+  }
+  return;
+}
+
+static void
+gst_qtp_stsd_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter)
+{
+  guint32 a[2];
+  gst_qtp_read_bytes(qtdemux,a,8);
+  GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_stsd_handler: %d entries in the table\n",GUINT32_FROM_BE(a[1]));
+  /* just put the rest of the atom into sample description table */
+  track_to_be->stsd = gst_qtp_read(qtdemux,atom->start + atom->size - qtdemux->bs_pos);
+}
+
+static void
+gst_qtp_stts_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter)
+{
+  guint32 a[2];
+  gst_qtp_read_bytes(qtdemux,a,8);
+  GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_stts_handler: %d entries in the table\n",GUINT32_FROM_BE(a[1]));
+  track_to_be->stts = gst_qtp_read(qtdemux,GUINT32_FROM_BE(a[1])*sizeof(GstQtpSttsRec));
+}
+static void
+gst_qtp_stsc_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter)
+{
+  guint32 a[2];
+  gst_qtp_read_bytes(qtdemux,a,8);
+  GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_stsc_handler: %d entries in the table\n",GUINT32_FROM_BE(a[1]));
+  track_to_be->stsc = gst_qtp_read(qtdemux,GUINT32_FROM_BE(a[1])*sizeof(GstQtpStscRec));
+}
+
+static void
+gst_qtp_stsz_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter)
+{
+  guint32 a[3];
+  gst_qtp_read_bytes(qtdemux,a,12);
+  GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_stsz_handler: %d entries in the table\n",GUINT32_FROM_BE(a[2]));
+  /* FIXME have to chech a[2], it contains size if all samples if they are the same size */
+  track_to_be->stsz = gst_qtp_read(qtdemux,GUINT32_FROM_BE(a[2])*sizeof(guint32));
+}
+
+static void
+gst_qtp_stco_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter)
+{
+  guint32 a[2];
+  gst_qtp_read_bytes(qtdemux,a,8);
+  GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_stco_handler: %d entries in the table\n",GUINT32_FROM_BE(a[1]));
+  track_to_be->stco = gst_qtp_read(qtdemux,GUINT32_FROM_BE(a[1])*sizeof(guint32));
+}
+
+static void
+gst_qtp_mdhd_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter) 
+{
+  guint32 a[4];
+  gst_qtp_read_bytes(qtdemux,a,16);
+  track_to_be->time_scale = GUINT32_FROM_BE(a[3]);
+  GST_INFO (GST_CAT_PLUGIN_INFO,"gst_qtp_mdhd_handler: time scale: %d\n",track_to_be->time_scale);
+}
+
+static gboolean
+gst_qtp_traverse(gpointer poffs,gpointer value,gpointer data)
+{
+  GstQtpSample * sample = (GstQtpSample*)value;
+  GstQTDemux * qtdemux = (GstQTDemux*)data;
+
+  if (qtdemux->bs_pos < sample->offset) {
+    gst_qtp_skip(qtdemux,sample->offset - qtdemux->bs_pos);
+    if (sample->track->pad && GST_PAD_IS_CONNECTED(sample->track->pad)) {
+      GstBuffer * buf;
+      buf = gst_qtp_read(qtdemux,sample->size);
+      GST_BUFFER_TIMESTAMP(buf) = sample->timestamp;
+      gst_pad_push(sample->track->pad,buf);
+    }
+  }
+  return FALSE; /* == keep going (TRUE to stop) */
+}
+
+static void
+gst_qtp_mdat_handler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter) 
+{
+  /* actually playing */
+  g_tree_foreach(qtdemux->samples,gst_qtp_traverse,qtdemux);
+}
diff --git a/gst/qtdemux/qtdemux.h b/gst/qtdemux/qtdemux.h
new file mode 100644 (file)
index 0000000..fa7e79f
--- /dev/null
@@ -0,0 +1,168 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef __GST_QTDEMUX_H__
+#define __GST_QTDEMUX_H__
+
+#include <gst/gst.h>
+#include <gst/bytestream/bytestream.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define GST_TYPE_QTDEMUX \
+  (gst_qtdemux_get_type())
+#define GST_QTDEMUX(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_QTDEMUX,GstQTDemux))
+#define GST_QTDEMUX_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_QTDEMUX,GstQTDemux))
+#define GST_IS_QTDEMUX(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_QTDEMUX))
+#define GST_IS_QTDEMUX_CLASS(obj) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_QTDEMUX))
+
+#define GST_QTDEMUX_MAX_AUDIO_PADS     8       
+#define GST_QTDEMUX_MAX_VIDEO_PADS     8       
+
+  /*
+   * smartass macross that turns guint32 into sequence of bytes separated with comma
+   * to be used in printf("%c%c%c%c",GST_FOURCC_TO_CHARSEQ(fourcc)) fashion
+   */
+#define GST_FOURCC_TO_CHARSEQ(f) f&0xff, (f>>8)&0xff , (f>>16)&0xff, f>>24
+
+typedef struct _GstQTDemux GstQTDemux;
+typedef struct _GstQTDemuxClass GstQTDemuxClass;
+
+typedef struct {
+  guint64 start;
+  guint64 size; /* if 0, lasts till the end of file */
+  guint32 type;
+} GstQtpAtom;
+
+typedef struct {
+  guint32 size;
+  guint32 type;
+} GstQtpAtomMinHeader;
+
+#define GST_QTP_CONTAINER_ATOM 1
+
+typedef void GstQtpAtomTypeHandler (GstQTDemux * qtdemux,GstQtpAtom * atom,gboolean enter);
+
+typedef struct {
+  guint32 flags;
+  GstQtpAtomTypeHandler * handler;
+} GstQtpAtomType;
+
+typedef struct {
+  guint64 offset;
+  guint32 size;
+  guint32 timestamp;
+  struct _GstQtpTrack * track;
+} GstQtpSample;
+
+typedef struct _GstQtpTrack {
+  guint32 format;
+  guint32 width;
+  guint32 height;
+  guint32 time_scale; /* units per second */
+  guint32 sample_duration; /* units in sample */
+
+  /* temporary buffers with sample tables */
+  GstBuffer * stsd, * stts, * stsc, * stsz, * stco;
+
+  /* this track samples in array */
+  GstQtpSample * samples;
+
+  GstPad * pad;
+} GstQtpTrack;
+
+typedef struct {
+  guint32 size;
+  guint32 format;
+  char reserved[6];
+  guint16 dataref;
+} __attribute__((packed)) /* FIXME may it wasn't necessary? */ GstQtpStsdRec;
+
+typedef struct {
+  guint32 size;
+  guint32 format;
+  char reserved[6];
+  guint16 dataref;
+  guint16 version;
+  guint16 rev_level;
+  guint32 vendor;
+  guint32 temporal_quality;
+  guint32 spatial_quality;
+  guint16 width;
+  guint16 height;
+  guint32 hres;
+  guint32 vres;
+  guint32 data_size;
+  guint16 frame_count; /* frames per sample */
+  guint32 compressor_name;
+  guint16 depth;
+  guint16 color_table_id;
+} __attribute__((packed)) /* FIXME may it wasn't necessary? */ GstQtpStsdVideRec;
+
+typedef struct {
+  guint32 count;
+  guint32 duration;
+} GstQtpSttsRec;
+
+typedef struct {
+  guint32 first_chunk;
+  guint32 samples_per_chunk;
+  guint32 sample_desc;
+} GstQtpStscRec;
+
+struct _GstQTDemux {
+  GstElement element;
+
+  /* pads */
+  GstPad *sinkpad;
+  GstPad *video_pad[GST_QTDEMUX_MAX_VIDEO_PADS];
+  int num_video_pads;
+
+  GstByteStream *bs;
+  guint64 bs_pos; /* current position in bs (coz bs won't tell) */
+
+  /* 
+   * nesting stack: everytime parser reads a header
+   * of a container atom it is added to the stack until 
+   * and removed whenever it's over (read completely)
+   */
+  GSList * nested;
+  int nested_cnt; /* just to make it simpler */
+
+  GList * tracks;
+  GTree * samples; /* samples of all the tracks ordered by the offset */
+};
+
+struct _GstQTDemuxClass {
+  GstElementClass parent_class;
+};
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __GST_QTDEMUX_H__ */