/* GStreamer * Copyright (C) <1999> Erik Walthinsen * * 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 #include #include "qtdemux.h" /* elementfactory information */ static GstElementDetails gst_qtdemux_details = { "quicktime parser", "Codec/Parser", "Parses a quicktime stream into audio and video substreams", VERSION, "A.Baguinski ", "(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;ibs = 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; ivideo_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")) 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_set_rank (factory, GST_ELEMENT_RANK_PRIMARY); 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 (gst_bytestream_peek_bytes (bs, (guint8**)&amh, 8) == 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 (gst_bytestream_peek_bytes (bs, (guint8**)&esize, 8) == 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 (gst_bytestream_peek_bytes (bs, (guint8**)&data, size) == 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 (gst_bytestream_read (bs, &buf, size) == 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; chunksamples[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); }