| /* 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 <stdlib.h> |
| #include <string.h> |
| #include "qtdemux.h" |
| |
| /* elementfactory information */ |
| static GstElementDetails |
| gst_qtdemux_details = |
| { |
| "quicktime parser", |
| "Codec/Parser", |
| "LGPL", |
| "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); |
| |
| /* we could get a NULL buffer, for example when the input could not be |
| * mmap'd */ |
| g_return_val_if_fail (data != NULL, NULL); |
| |
| /* 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,%" G_GUINT64_FORMAT ",%" G_GUINT64_FORMAT")\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; |
| 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_LINKED(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); |
| } |