2002-05-01 18:41:59 +00:00
|
|
|
/* 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",
|
2002-06-08 09:15:07 +00:00
|
|
|
"Codec/Parser",
|
2002-05-01 18:41:59 +00:00
|
|
|
"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);
|
2002-05-31 08:24:31 +00:00
|
|
|
gst_element_factory_set_rank (factory, GST_ELEMENT_RANK_PRIMARY);
|
2002-05-01 18:41:59 +00:00
|
|
|
|
|
|
|
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) {
|
2002-05-15 19:08:49 +00:00
|
|
|
if (gst_bytestream_peek_bytes (bs, (guint8**)&amh, 8) == 8) {
|
2002-05-01 18:41:59 +00:00
|
|
|
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 */
|
2002-05-15 19:08:49 +00:00
|
|
|
if (gst_bytestream_peek_bytes (bs, (guint8**)&esize, 8) == 8) {
|
2002-05-01 18:41:59 +00:00
|
|
|
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;
|
2002-05-15 19:08:49 +00:00
|
|
|
|
2002-05-01 18:41:59 +00:00
|
|
|
do {
|
2002-05-15 19:08:49 +00:00
|
|
|
if (gst_bytestream_peek_bytes (bs, (guint8**)&data, size) == size) {
|
2002-05-01 18:41:59 +00:00
|
|
|
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 {
|
2002-05-15 19:08:49 +00:00
|
|
|
if (gst_bytestream_read (bs, &buf, size) == size) {
|
2002-05-01 18:41:59 +00:00
|
|
|
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);
|
|
|
|
}
|