From 12e856c26d690c02cc05a2e945b49e2df538307a Mon Sep 17 00:00:00 2001 From: "Ronald S. Bultje" Date: Sun, 7 Dec 2003 20:00:41 +0000 Subject: [PATCH] Riff, EBML, fourcc etc. work. Not fully finished, but better than what we used to have and definately worth a first b... Original commit message from CVS: Riff, EBML, fourcc etc. work. Not fully finished, but better than what we used to have and definately worth a first broad testing. I've revived rifflib. Rifflib used to be a bytestream-for-riff, which just dup'ed bytestream. I've rewritten rifflib to be a modern riff- chunk parser that uses bytestream fully, plus adds some extra functions so that riff file parsing becomes extremely easy. It also contains some small usability functions for strh/strf and metadata parsing. Note that it doesn't use the new tagging yet, that's a TODO. Avidemux has been rewritten to use this. I think we all agreed that avidemux was pretty much a big mess, which is because it used all sort of bytestream magic all around the place. It was just ugly. This is a lot nicer, very complete and safe. I think this is far more robust than what the old avidemux could ever have been. Of course, it might contain bugs, please let me know. EBML writing has also been implemented. This is useful for matroska. I'm intending to modify avidemux (with a riffwriter) similarly. Maybe I'll change wavparse/-enc too to use rifflib. Lastly, several plugins have been modified to use rifflib's fourcc parsing instead of their own. this puts fourcc parsing in one central place, which should make it a lot simpler to add new fourccs. We might want to move this to its own lib instead of rifflib. Enjoy! --- gst/avi/Makefile.am | 11 +- gst/avi/avi-ids.h | 48 + gst/avi/gstavi.c | 53 + gst/avi/gstavidemux.c | 2610 ++++++++++++++++++------------------------- gst/avi/gstavidemux.h | 122 +- gst/avi/gstavimux.h | 3 +- gst/matroska/Makefile.am | 8 +- gst/matroska/ebml-read.c | 127 ++- gst/matroska/ebml-read.h | 4 +- gst/matroska/ebml-write.c | 580 ++++++++++ gst/matroska/ebml-write.h | 126 +++ gst/matroska/matroska-ids.h | 4 +- gst/matroska/matroska-mux.c | 961 ++++++++++++++++ gst/matroska/matroska-mux.h | 103 ++ gst/matroska/matroska.c | 4 +- 15 files changed, 3120 insertions(+), 1644 deletions(-) create mode 100644 gst/avi/avi-ids.h create mode 100644 gst/avi/gstavi.c create mode 100644 gst/matroska/ebml-write.c create mode 100644 gst/matroska/ebml-write.h create mode 100644 gst/matroska/matroska-mux.c create mode 100644 gst/matroska/matroska-mux.h diff --git a/gst/avi/Makefile.am b/gst/avi/Makefile.am index 0b81ccc..cbea2b3 100644 --- a/gst/avi/Makefile.am +++ b/gst/avi/Makefile.am @@ -1,11 +1,14 @@ - plugin_LTLIBRARIES = libgstavi.la -libgstavi_la_SOURCES = gstavidemux.c gstavimux.c +libgstavi_la_SOURCES = \ + gstavi.c \ + gstavidemux.c \ + gstavimux.c noinst_HEADERS = \ - gstavimux.h \ - gstavidemux.h + avi-ids.h \ + gstavimux.h \ + gstavidemux.h libgstavi_la_CFLAGS = $(GST_CFLAGS) libgstavi_la_LIBADD = diff --git a/gst/avi/avi-ids.h b/gst/avi/avi-ids.h new file mode 100644 index 0000000..f9e2dd3 --- /dev/null +++ b/gst/avi/avi-ids.h @@ -0,0 +1,48 @@ +/* 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. + */ + +#ifndef __GST_AVI_H__ +#define __GST_AVI_H__ + +#include + +typedef struct _gst_riff_avih { + guint32 us_frame; /* microsec per frame */ + guint32 max_bps; /* byte/s overall */ + guint32 pad_gran; /* pad_granularity */ + guint32 flags; +/* flags values */ +#define GST_RIFF_AVIH_HASINDEX 0x00000010 /* has idx1 chunk */ +#define GST_RIFF_AVIH_MUSTUSEINDEX 0x00000020 /* must use idx1 chunk to determine order */ +#define GST_RIFF_AVIH_ISINTERLEAVED 0x00000100 /* AVI file is interleaved */ +#define GST_RIFF_AVIH_WASCAPTUREFILE 0x00010000 /* specially allocated used for capturing real time video */ +#define GST_RIFF_AVIH_COPYRIGHTED 0x00020000 /* contains copyrighted data */ + guint32 tot_frames; /* # of frames (all) */ + guint32 init_frames; /* initial frames (???) */ + guint32 streams; + guint32 bufsize; /* suggested buffer size */ + guint32 width; + guint32 height; + guint32 scale; + guint32 rate; + guint32 start; + guint32 length; +} gst_riff_avih; + +#endif /* __GST_AVI_H__ */ diff --git a/gst/avi/gstavi.c b/gst/avi/gstavi.c new file mode 100644 index 0000000..da30d43 --- /dev/null +++ b/gst/avi/gstavi.c @@ -0,0 +1,53 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * + * gstavi.c: plugin registering + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstavidemux.h" +#include "gstavimux.h" + +static gboolean +plugin_init (GstPlugin *plugin) +{ + if (!gst_library_load ("riff")) + return FALSE; + + return (gst_element_register (plugin, "avidemux", + GST_RANK_PRIMARY, + GST_TYPE_AVI_DEMUX) && + gst_element_register (plugin, "avimux", + GST_RANK_NONE, + GST_TYPE_AVIMUX)); +} + +GST_PLUGIN_DEFINE ( + GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "avi", + "AVI stream handling", + plugin_init, + VERSION, + "LGPL", + GST_PACKAGE, + GST_ORIGIN +) diff --git a/gst/avi/gstavidemux.c b/gst/avi/gstavidemux.c index ad4531a..5f19fce 100644 --- a/gst/avi/gstavidemux.c +++ b/gst/avi/gstavidemux.c @@ -17,15 +17,15 @@ * Boston, MA 02111-1307, USA. */ - -/* #define GST_DEBUG_ENABLED */ #ifdef HAVE_CONFIG_H #include "config.h" #endif + #include +#include "gst/riff/riff-media.h" #include "gstavidemux.h" -#include "gstavimux.h" +#include "avi-ids.h" GST_DEBUG_CATEGORY_STATIC (avidemux_debug); #define GST_CAT_DEFAULT avidemux_debug @@ -38,8 +38,6 @@ enum { enum { ARG_0, - ARG_BITRATE, - ARG_METADATA, ARG_STREAMINFO, /* FILL ME */ }; @@ -55,10 +53,11 @@ GST_PAD_TEMPLATE_FACTORY (sink_templ, ) ); -static void gst_avi_demux_base_init (gpointer g_class); +static void gst_avi_demux_base_init (GstAviDemuxClass *klass); static void gst_avi_demux_class_init (GstAviDemuxClass *klass); -static void gst_avi_demux_init (GstAviDemux *avi_demux); +static void gst_avi_demux_init (GstAviDemux *avi); +static void gst_avi_demux_reset (GstAviDemux *avi); static void gst_avi_demux_loop (GstElement *element); static gboolean gst_avi_demux_send_event (GstElement *element, @@ -90,15 +89,7 @@ static void gst_avi_demux_get_property (GObject *object, GValue *value, GParamSpec *pspec); -static GstCaps * gst_avi_demux_audio_caps (guint16 codec_id, - gst_riff_strf_auds *strf, GstAviDemux *avi_demux); -static GstCaps * gst_avi_demux_video_caps (guint32 codec_fcc, - gst_riff_strh *strh, gst_riff_strf_vids *strf, - GstAviDemux *avi_demux); -static GstCaps * gst_avi_demux_iavs_caps (void); - -static GstPadTemplate *videosrctempl, *audiosrctempl; -static GstElementClass *parent_class = NULL; +static GstRiffReadClass *parent_class = NULL; /*static guint gst_avi_demux_signals[LAST_SIGNAL] = { 0 }; */ GType @@ -108,88 +99,60 @@ gst_avi_demux_get_type(void) if (!avi_demux_type) { static const GTypeInfo avi_demux_info = { - sizeof(GstAviDemuxClass), - gst_avi_demux_base_init, + sizeof (GstAviDemuxClass), + (GBaseInitFunc) gst_avi_demux_base_init, NULL, - (GClassInitFunc)gst_avi_demux_class_init, + (GClassInitFunc) gst_avi_demux_class_init, NULL, NULL, - sizeof(GstAviDemux), + sizeof (GstAviDemux), 0, - (GInstanceInitFunc)gst_avi_demux_init, + (GInstanceInitFunc) gst_avi_demux_init, }; - avi_demux_type = g_type_register_static(GST_TYPE_ELEMENT, "GstAviDemux", &avi_demux_info, 0); + + avi_demux_type = + g_type_register_static (GST_TYPE_RIFF_READ, + "GstAviDemux", + &avi_demux_info, 0); } + return avi_demux_type; } static void -gst_avi_demux_base_init (gpointer g_class) +gst_avi_demux_base_init (GstAviDemuxClass *klass) { static GstElementDetails gst_avi_demux_details = GST_ELEMENT_DETAILS ( "Avi demuxer", "Codec/Demuxer", "Demultiplex an avi file into audio and video", "Erik Walthinsen \n" - "Wim Taymans " + "Wim Taymans \n" + "Ronald Bultje " ); - static guint32 vid_list[] = { - GST_MAKE_FOURCC('I','4','2','0'), - GST_MAKE_FOURCC('Y','U','Y','2'), - GST_MAKE_FOURCC('M','J','P','G'), - GST_MAKE_FOURCC('D','V','S','D'), - GST_MAKE_FOURCC('W','M','V','1'), - GST_MAKE_FOURCC('W','M','V','2'), - GST_MAKE_FOURCC('M','P','G','4'), - GST_MAKE_FOURCC('M','P','4','2'), - GST_MAKE_FOURCC('M','P','4','3'), - GST_MAKE_FOURCC('H','F','Y','U'), - GST_MAKE_FOURCC('D','I','V','3'), - GST_MAKE_FOURCC('M','P','E','G'), - GST_MAKE_FOURCC('H','2','6','3'), - GST_MAKE_FOURCC('D','I','V','X'), - GST_MAKE_FOURCC('X','V','I','D'), - GST_MAKE_FOURCC('3','I','V','1'), - 0 /* end */ - }; - static gint aud_list[] = { - GST_RIFF_WAVE_FORMAT_MPEGL3, - GST_RIFF_WAVE_FORMAT_MPEGL12, - GST_RIFF_WAVE_FORMAT_PCM, - GST_RIFF_WAVE_FORMAT_VORBIS1, - GST_RIFF_WAVE_FORMAT_A52, - GST_RIFF_WAVE_FORMAT_ALAW, - GST_RIFF_WAVE_FORMAT_MULAW, - -1 /* end */ - }; - GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); - gint i = 0; - GstCaps *audcaps = NULL, *vidcaps = NULL, *temp; + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstPadTemplate *videosrctempl, *audiosrctempl; + GstCaps *audcaps, *vidcaps; - for (i = 0; aud_list[i] != -1; i++) { - temp = gst_avi_demux_audio_caps (aud_list[i], NULL, NULL); - audcaps = gst_caps_append (audcaps, temp); - } + audcaps = gst_riff_create_audio_template_caps (); audiosrctempl = gst_pad_template_new ("audio_%02d", GST_PAD_SRC, GST_PAD_SOMETIMES, audcaps, NULL); - for (i = 0; vid_list[i] != 0; i++) { - temp = gst_avi_demux_video_caps (vid_list[i], NULL, NULL, NULL); - vidcaps = gst_caps_append (vidcaps, temp); - } - vidcaps = gst_caps_append (vidcaps, - gst_avi_demux_iavs_caps ()); + + vidcaps = gst_caps_append ( + gst_riff_create_video_template_caps (), + gst_riff_create_iavs_template_caps ()); videosrctempl = gst_pad_template_new ("video_%02d", GST_PAD_SRC, GST_PAD_SOMETIMES, vidcaps, NULL); + gst_element_class_add_pad_template (element_class, audiosrctempl); gst_element_class_add_pad_template (element_class, videosrctempl); gst_element_class_add_pad_template (element_class, GST_PAD_TEMPLATE_GET (sink_templ)); gst_element_class_set_details (element_class, &gst_avi_demux_details); - } static void @@ -201,17 +164,14 @@ gst_avi_demux_class_init (GstAviDemuxClass *klass) gobject_class = (GObjectClass*)klass; gstelement_class = (GstElementClass*)klass; - g_object_class_install_property (G_OBJECT_CLASS(klass), ARG_BITRATE, - g_param_spec_long ("bitrate","bitrate","bitrate", - G_MINLONG, G_MAXLONG, 0, G_PARAM_READABLE)); /* CHECKME */ - g_object_class_install_property (gobject_class, ARG_METADATA, - g_param_spec_boxed ("metadata", "Metadata", "Metadata", - GST_TYPE_CAPS, G_PARAM_READABLE)); g_object_class_install_property (gobject_class, ARG_STREAMINFO, g_param_spec_boxed ("streaminfo", "Streaminfo", "Streaminfo", GST_TYPE_CAPS, G_PARAM_READABLE)); - parent_class = g_type_class_ref (GST_TYPE_ELEMENT); + GST_DEBUG_CATEGORY_INIT (avidemux_debug, "avidemux", + 0, "Demuxer for AVI streams"); + + parent_class = g_type_class_ref (GST_TYPE_RIFF_READ); gobject_class->get_property = gst_avi_demux_get_property; @@ -220,295 +180,57 @@ gst_avi_demux_class_init (GstAviDemuxClass *klass) } static void -gst_avi_demux_init (GstAviDemux *avi_demux) -{ - GST_FLAG_SET (avi_demux, GST_ELEMENT_EVENT_AWARE); - - avi_demux->sinkpad = gst_pad_new_from_template ( - GST_PAD_TEMPLATE_GET (sink_templ), "sink"); - gst_element_add_pad (GST_ELEMENT (avi_demux), avi_demux->sinkpad); - - gst_element_set_loop_function (GST_ELEMENT (avi_demux), gst_avi_demux_loop); -} - -static gboolean -gst_avi_demux_avih (GstAviDemux *avi_demux) -{ - gst_riff_avih *avih; - guint8 *avihdata; - GstByteStream *bs = avi_demux->bs; - guint32 got_bytes; - - got_bytes = gst_bytestream_peek_bytes (bs, &avihdata, sizeof (gst_riff_avih)); - avih = (gst_riff_avih *) avihdata; - - if (got_bytes == sizeof (gst_riff_avih)) { - avi_demux->avih.us_frame = GUINT32_FROM_LE (avih->us_frame); - avi_demux->avih.max_bps = GUINT32_FROM_LE (avih->max_bps); - avi_demux->avih.pad_gran = GUINT32_FROM_LE (avih->pad_gran); - avi_demux->avih.flags = GUINT32_FROM_LE (avih->flags); - avi_demux->avih.tot_frames = GUINT32_FROM_LE (avih->tot_frames); - avi_demux->avih.init_frames = GUINT32_FROM_LE (avih->init_frames); - avi_demux->avih.streams = GUINT32_FROM_LE (avih->streams); - avi_demux->avih.bufsize = GUINT32_FROM_LE (avih->bufsize); - avi_demux->avih.width = GUINT32_FROM_LE (avih->width); - avi_demux->avih.height = GUINT32_FROM_LE (avih->height); - avi_demux->avih.scale = GUINT32_FROM_LE (avih->scale); - avi_demux->avih.rate = GUINT32_FROM_LE (avih->rate); - avi_demux->avih.start = GUINT32_FROM_LE (avih->start); - avi_demux->avih.length = GUINT32_FROM_LE (avih->length); - - GST_INFO ( "gst_avi_demux: avih tag found"); - GST_INFO ( "gst_avi_demux: us_frame %d", avi_demux->avih.us_frame); - GST_INFO ( "gst_avi_demux: max_bps %d", avi_demux->avih.max_bps); - GST_INFO ( "gst_avi_demux: pad_gran %d", avi_demux->avih.pad_gran); - GST_INFO ( "gst_avi_demux: flags 0x%08x", avi_demux->avih.flags); - GST_INFO ( "gst_avi_demux: tot_frames %d", avi_demux->avih.tot_frames); - GST_INFO ( "gst_avi_demux: init_frames %d", avi_demux->avih.init_frames); - GST_INFO ( "gst_avi_demux: streams %d", avi_demux->avih.streams); - GST_INFO ( "gst_avi_demux: bufsize %d", avi_demux->avih.bufsize); - GST_INFO ( "gst_avi_demux: width %d", avi_demux->avih.width); - GST_INFO ( "gst_avi_demux: height %d", avi_demux->avih.height); - GST_INFO ( "gst_avi_demux: scale %d", avi_demux->avih.scale); - GST_INFO ( "gst_avi_demux: rate %d", avi_demux->avih.rate); - GST_INFO ( "gst_avi_demux: start %d", avi_demux->avih.start); - GST_INFO ( "gst_avi_demux: length %d", avi_demux->avih.length); - - return TRUE; - } - return FALSE; -} - -static gboolean -gst_avi_demux_strh (GstAviDemux *avi_demux) -{ - gst_riff_strh *strh; - guint8 *strhdata; - GstByteStream *bs = avi_demux->bs; - guint32 got_bytes; - - got_bytes = gst_bytestream_peek_bytes (bs, &strhdata, sizeof (gst_riff_strh)); - strh = (gst_riff_strh *) strhdata; - - if (got_bytes == sizeof (gst_riff_strh)) { - avi_stream_context *target; - - avi_demux->fcc_type = GUINT32_FROM_LE (strh->type); - - target = &avi_demux->stream[avi_demux->num_streams]; - - target->num = avi_demux->num_streams; - - target->strh.type = avi_demux->fcc_type; - target->strh.fcc_handler = GUINT32_FROM_LE (strh->fcc_handler); - target->strh.flags = GUINT32_FROM_LE (strh->flags); - target->strh.priority = GUINT32_FROM_LE (strh->priority); - target->strh.init_frames = GUINT32_FROM_LE (strh->init_frames); - target->strh.scale = GUINT32_FROM_LE (strh->scale); - target->strh.rate = GUINT32_FROM_LE (strh->rate); - target->strh.start = GUINT32_FROM_LE (strh->start); - target->strh.length = GUINT32_FROM_LE (strh->length); - target->strh.bufsize = GUINT32_FROM_LE (strh->bufsize); - target->strh.quality = GUINT32_FROM_LE (strh->quality); - target->strh.samplesize = GUINT32_FROM_LE (strh->samplesize); - - if (!target->strh.scale) - target->strh.scale = 1; /* avoid division by zero */ - if (!target->strh.rate) - target->strh.rate = 1; /* avoid division by zero */ - - GST_INFO ( "gst_avi_demux: strh tag found"); - GST_INFO ( "gst_avi_demux: type 0x%08x (%s)", - target->strh.type, gst_riff_id_to_fourcc (strh->type)); - GST_INFO ( "gst_avi_demux: fcc_handler 0x%08x (%s)", - target->strh.fcc_handler, gst_riff_id_to_fourcc (strh->fcc_handler)); - GST_INFO ( "gst_avi_demux: flags 0x%08x", strh->flags); - GST_INFO ( "gst_avi_demux: priority %d", target->strh.priority); - GST_INFO ( "gst_avi_demux: init_frames %d", target->strh.init_frames); - GST_INFO ( "gst_avi_demux: scale %d", target->strh.scale); - GST_INFO ( "gst_avi_demux: rate %d", target->strh.rate); - GST_INFO ( "gst_avi_demux: start %d", target->strh.start); - GST_INFO ( "gst_avi_demux: length %d", target->strh.length); - GST_INFO ( "gst_avi_demux: bufsize %d", target->strh.bufsize); - GST_INFO ( "gst_avi_demux: quality %d", target->strh.quality); - GST_INFO ( "gst_avi_demux: samplesize %d", target->strh.samplesize); - - target->delay = 0LL; - target->total_bytes = 0LL; - target->total_frames = 0; - target->end_pos = -1; - target->current_frame = 0; - target->current_byte = 0; - target->need_flush = FALSE; - target->skip = 0; - - avi_demux->avih.bufsize = MAX (avi_demux->avih.bufsize, target->strh.bufsize); - - return TRUE; - } - return FALSE; -} - -static void -gst_avi_demux_dmlh (GstAviDemux *avi_demux) +gst_avi_demux_init (GstAviDemux *avi) { - gst_riff_dmlh *dmlh; - guint8 *dmlhdata; - GstByteStream *bs = avi_demux->bs; - guint32 got_bytes; + GST_FLAG_SET (avi, GST_ELEMENT_EVENT_AWARE); - got_bytes = gst_bytestream_peek_bytes (bs, &dmlhdata, sizeof (gst_riff_dmlh)); - dmlh = (gst_riff_dmlh *) dmlhdata; -} - -static void -gst_avi_demux_strn (GstAviDemux *avi_demux, gint len) -{ - gchar *name; - guint8 *namedata; - GstByteStream *bs = avi_demux->bs; - guint32 got_bytes; + avi->sinkpad = gst_pad_new_from_template ( + GST_PAD_TEMPLATE_GET (sink_templ), "sink"); + gst_element_add_pad (GST_ELEMENT (avi), avi->sinkpad); + GST_RIFF_READ (avi)->sinkpad = avi->sinkpad; - got_bytes = gst_bytestream_peek_bytes (bs, &namedata, len); - name = (gchar *) namedata; - if (got_bytes != len) - return; + gst_element_set_loop_function (GST_ELEMENT (avi), gst_avi_demux_loop); + gst_avi_demux_reset (avi); - GST_DEBUG ("Stream name: \"%s\"", name); + avi->streaminfo = NULL; + avi->index_entries = NULL; + memset (&avi->stream, 0, sizeof (avi->stream)); } static void -gst_avi_demux_metadata (GstAviDemux *avi_demux, gint len) +gst_avi_demux_reset (GstAviDemux *avi) { - guint32 got_bytes; - GstByteStream *bs = avi_demux->bs; - gst_riff_chunk *temp_chunk, chunk; - guint8 *tempdata; - gchar *name, *type; - GstProps *props; - GstPropsEntry *entry; - - props = gst_props_empty_new (); - - while (len > 0) { - got_bytes = gst_bytestream_peek_bytes (bs, &tempdata, sizeof (gst_riff_chunk)); - temp_chunk = (gst_riff_chunk *) tempdata; - - /* fixup for our big endian friends */ - chunk.id = GUINT32_FROM_LE (temp_chunk->id); - chunk.size = GUINT32_FROM_LE (temp_chunk->size); + gint i; - gst_bytestream_flush (bs, sizeof (gst_riff_chunk)); - if (got_bytes != sizeof (gst_riff_chunk)) - return; - len -= sizeof (gst_riff_chunk); + for (i = 0; i < avi->num_streams; i++) { + g_free (avi->stream[i].strh); + gst_element_remove_pad (GST_ELEMENT (avi), avi->stream[i].pad); + } + memset (&avi->stream, 0, sizeof (avi->stream)); - /* don't care about empty entries - move on */ - if (chunk.size == 0) - continue; + avi->num_streams = 0; + avi->num_v_streams = 0; + avi->num_a_streams = 0; - got_bytes = gst_bytestream_peek_bytes (bs, &tempdata, chunk.size); - name = (gchar *) tempdata; - gst_bytestream_flush (bs, (chunk.size + 1) & ~1); - if (got_bytes != chunk.size) - return; - len -= ((chunk.size + 1) & ~1); - - /* we now have an info string in 'name' of type 'chunk.id' - find 'type' */ - switch (chunk.id) { - case GST_RIFF_INFO_IARL: - type = "Location"; - break; - case GST_RIFF_INFO_IART: - type = "Artist"; - break; - case GST_RIFF_INFO_ICMS: - type = "Commissioner"; - break; - case GST_RIFF_INFO_ICMT: - type = "Comment"; - break; - case GST_RIFF_INFO_ICOP: - type = "Copyright"; - break; - case GST_RIFF_INFO_ICRD: - type = "Creation Date"; - break; - case GST_RIFF_INFO_ICRP: - type = "Cropped"; - break; - case GST_RIFF_INFO_IDIM: - type = "Dimensions"; - break; - case GST_RIFF_INFO_IDPI: - type = "Dots per Inch"; - break; - case GST_RIFF_INFO_IENG: - type = "Engineer"; - break; - case GST_RIFF_INFO_IGNR: - type = "Genre"; - break; - case GST_RIFF_INFO_IKEY: - type = "Keywords"; - break; - case GST_RIFF_INFO_ILGT: - type = "Lightness"; - break; - case GST_RIFF_INFO_IMED: - type = "Medium"; - break; - case GST_RIFF_INFO_INAM: - type = "Title"; /* "Name" */ - break; - case GST_RIFF_INFO_IPLT: - type = "Palette"; - break; - case GST_RIFF_INFO_IPRD: - type = "Product"; - break; - case GST_RIFF_INFO_ISBJ: - type = "Subject"; - break; - case GST_RIFF_INFO_ISFT: - type = "Encoder"; /* "Software" */ - break; - case GST_RIFF_INFO_ISHP: - type = "Sharpness"; - break; - case GST_RIFF_INFO_ISRC: - type = "Source"; - break; - case GST_RIFF_INFO_ISRF: - type = "Source Form"; - break; - case GST_RIFF_INFO_ITCH: - type = "Technician"; - break; - default: - type = NULL; - break; - } + avi->state = GST_AVI_DEMUX_START; + avi->level_up = 0; - if (type) { - /* create props entry */ - entry = gst_props_entry_new (type, GST_PROPS_STRING (name)); - gst_props_add_entry (props, entry); - } + if (avi->index_entries) { + g_free (avi->index_entries); + avi->index_entries = NULL; } + avi->index_size = 0; - gst_props_debug(props); + avi->num_frames = 0; + avi->us_per_frame = 0; - gst_caps_replace_sink (&avi_demux->metadata, - gst_caps_new("avi_metadata", - "application/x-gst-metadata", - props)); + avi->seek_offset = (guint64) -1; - g_object_notify(G_OBJECT(avi_demux), "metadata"); + gst_caps_replace (&avi->streaminfo, NULL); } static void -gst_avi_demux_streaminfo (GstAviDemux *avi_demux) +gst_avi_demux_streaminfo (GstAviDemux *avi) { GstProps *props; @@ -516,890 +238,231 @@ gst_avi_demux_streaminfo (GstAviDemux *avi_demux) /* compression formats are added later - a bit hacky */ - gst_caps_replace_sink (&avi_demux->streaminfo, - gst_caps_new("avi_streaminfo", - "application/x-gst-streaminfo", - props)); + gst_caps_replace_sink (&avi->streaminfo, + gst_caps_new ("avi_streaminfo", + "application/x-gst-streaminfo", + props)); - /*g_object_notify(G_OBJECT(avi_demux), "streaminfo");*/ + /*g_object_notify(G_OBJECT(avi), "streaminfo");*/ } -/* video/audio pad/caps stuff */ - -#ifdef G_HAVE_ISO_VARARGS - -#define GST_AVI_VID_CAPS_NEW(name, mimetype, ...) \ - (strf != NULL) ? \ - GST_CAPS_NEW (name, \ - mimetype, \ - "width", GST_PROPS_INT (width), \ - "height", GST_PROPS_INT (height), \ - "framerate", GST_PROPS_FLOAT (framerate), \ - __VA_ARGS__) \ - : \ - GST_CAPS_NEW (name, \ - mimetype, \ - "width", GST_PROPS_INT_RANGE (16, 4096), \ - "height", GST_PROPS_INT_RANGE (16, 4096), \ - "framerate", GST_PROPS_FLOAT_RANGE (0., G_MAXFLOAT), \ - __VA_ARGS__) - -#elif defined(G_HAVE_GNUC_VARARGS) - -#define GST_AVI_VID_CAPS_NEW(name, mimetype, props...) \ - (strf != NULL) ? \ - GST_CAPS_NEW (name, \ - mimetype, \ - "width", GST_PROPS_INT (width), \ - "height", GST_PROPS_INT (height), \ - "framerate", GST_PROPS_FLOAT (framerate), \ - ##props) \ - : \ - GST_CAPS_NEW (name, \ - mimetype, \ - "width", GST_PROPS_INT_RANGE (16, 4096), \ - "height", GST_PROPS_INT_RANGE (16, 4096), \ - "framerate", GST_PROPS_FLOAT_RANGE (0., G_MAXFLOAT), \ - ##props) -#endif - -static GstCaps * -gst_avi_demux_video_caps (guint32 codec_fcc, - gst_riff_strh *strh, - gst_riff_strf_vids *strf, - GstAviDemux *avi_demux) +static gst_avi_index_entry * +gst_avi_demux_index_next (GstAviDemux *avi, + gint stream_nr, + gint start, + guint32 flags) { - GstCaps *caps = NULL; - gchar *codecname = NULL; - gint width = -1, height = -1; - gdouble framerate = 0.; - - if (strf != NULL) { - width = GUINT32_FROM_LE (strf->width); - height = GUINT32_FROM_LE (strf->height); - } - if (strh != NULL) { - framerate = 1. * GUINT32_FROM_LE (strh->rate) / - GUINT32_FROM_LE (strh->scale); /* fps */ - } - - switch (codec_fcc) { - case GST_MAKE_FOURCC('I','4','2','0'): - case GST_MAKE_FOURCC('Y','U','Y','2'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src_raw", - "video/x-raw-yuv", - "format", GST_PROPS_FOURCC (codec_fcc) - ); - codecname = g_strdup_printf("Raw Video (" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; - - case GST_MAKE_FOURCC('M','J','P','G'): /* YUY2 MJPEG */ - case GST_MAKE_FOURCC('J','P','E','G'): /* generic (mostly RGB) MJPEG */ - case GST_MAKE_FOURCC('P','I','X','L'): /* Miro/Pinnacle fourccs */ - case GST_MAKE_FOURCC('V','I','X','L'): /* Miro/Pinnacle fourccs */ - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src_jpeg", - "video/x-jpeg", - NULL - ); - codecname = g_strdup_printf("Motion-JPEG (" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; - - case GST_MAKE_FOURCC('H','F','Y','U'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src_hfyu", - "video/x-huffyuv", - NULL - ); - codecname = g_strdup_printf("HuffYUV (" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; - - case GST_MAKE_FOURCC('M','P','E','G'): - case GST_MAKE_FOURCC('M','P','G','I'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src_mpeg", - "video/mpeg", - "systemstream", GST_PROPS_BOOLEAN (FALSE), - "mpegversion", GST_PROPS_BOOLEAN (1) - ); - codecname = g_strdup_printf("MPEG-1 (" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; - - case GST_MAKE_FOURCC('H','2','6','3'): - case GST_MAKE_FOURCC('i','2','6','3'): - case GST_MAKE_FOURCC('L','2','6','3'): - case GST_MAKE_FOURCC('M','2','6','3'): - case GST_MAKE_FOURCC('V','D','O','W'): - case GST_MAKE_FOURCC('V','I','V','O'): - case GST_MAKE_FOURCC('x','2','6','3'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src_263", - "video/x-h263", - NULL - ); - codecname = g_strdup_printf("H263-compatible (" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; + gint i; + gst_avi_index_entry *entry = NULL; - case GST_MAKE_FOURCC('D','I','V','3'): - case GST_MAKE_FOURCC('D','I','V','4'): - case GST_MAKE_FOURCC('D','I','V','5'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src_divx3", - "video/x-divx", - "divxversion", GST_PROPS_INT(3) - ); - codecname = g_strdup_printf("DivX-3.x (" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; + for (i = start; i < avi->index_size; i++) { + entry = &avi->index_entries[i]; - case GST_MAKE_FOURCC('d','i','v','x'): - case GST_MAKE_FOURCC('D','I','V','X'): - case GST_MAKE_FOURCC('D','X','5','0'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src_divx5", - "video/x-divx", - "divxversion", GST_PROPS_INT(5) - ); - codecname = g_strdup_printf("DivX 4.x/5.x (" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); + if (entry->stream_nr == stream_nr && (entry->flags & flags) == flags) { break; + } + } - case GST_MAKE_FOURCC('X','V','I','D'): - case GST_MAKE_FOURCC('x','v','i','d'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src", - "video/x-xvid", - NULL - ); - codecname = g_strdup_printf("XviD (" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; + return entry; +} - case GST_MAKE_FOURCC('M','P','G','4'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src", - "video/x-msmpeg", - "msmpegversion", GST_PROPS_INT (41) - ); - codecname = g_strdup_printf("MS MPEG-4.1 (" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; +static gst_avi_index_entry * +gst_avi_demux_index_entry_for_time (GstAviDemux *avi, + gint stream_nr, + guint64 time, + guint32 flags) +{ + gst_avi_index_entry *entry = NULL, *last_entry = NULL; + gint i; - case GST_MAKE_FOURCC('M','P','4','2'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src", - "video/x-msmpeg", - "msmpegversion", GST_PROPS_INT (42) - ); - codecname = g_strdup_printf("MS MPEG-4.2 (" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; + i = -1; + do { + entry = gst_avi_demux_index_next (avi, stream_nr, i + 1, flags); + if (!entry) + return NULL; - case GST_MAKE_FOURCC('M','P','4','3'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src", - "video/x-msmpeg", - "msmpegversion", GST_PROPS_INT (43) - ); - codecname = g_strdup_printf("MS MPEG-4.3 (" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; + i = entry->index_nr; - case GST_MAKE_FOURCC('3','I','V','1'): - case GST_MAKE_FOURCC('3','I','V','2'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src_3ivx", - "video/x-3ivx", - NULL - ); - codecname = g_strdup_printf("3ivX (" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; + if (entry->ts <= time) { + last_entry = entry; + } + } while (entry->ts <= time); - case GST_MAKE_FOURCC('D','V','S','D'): - case GST_MAKE_FOURCC('d','v','s','d'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src", - "video/x-dv", - "systemstream", GST_PROPS_BOOLEAN (FALSE) - ); - codecname = g_strdup_printf("Digital Video type 2 (" GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; + return last_entry; +} - case GST_MAKE_FOURCC('W','M','V','1'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src_wmv1", - "video/x-wmv", - "wmvversion", GST_PROPS_INT (1) - ); - codecname = g_strdup_printf("Windows Media Format 1 (" - GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; +static gst_avi_index_entry * +gst_avi_demux_index_entry_for_byte (GstAviDemux *avi, + gint stream_nr, + guint64 byte, + guint32 flags) +{ + gst_avi_index_entry *entry = NULL, *last_entry = NULL; + gint i; - case GST_MAKE_FOURCC('W','M','V','2'): - caps = GST_AVI_VID_CAPS_NEW ( - "avidemux_video_src_wmv2", - "video/x-wmv", - "wmvversion", GST_PROPS_INT (2) - ); - codecname = g_strdup_printf("Windows Media Format 2 (" - GST_FOURCC_FORMAT ")", - GST_FOURCC_ARGS(codec_fcc)); - break; + i = -1; + do { + entry = gst_avi_demux_index_next (avi, stream_nr, i + 1, flags); + if (!entry) + return NULL; - default: - g_warning ("avidemux: unkown video format " GST_FOURCC_FORMAT, - GST_FOURCC_ARGS(codec_fcc)); - break; - } + i = entry->index_nr; - /* set video codec info on streaminfo caps */ - if (avi_demux != NULL && codecname != NULL) { - GstPropsEntry *entry; - entry = gst_props_entry_new("videocodec", - GST_PROPS_STRING(codecname)); - gst_props_add_entry(avi_demux->streaminfo->properties, entry); - } - if (codecname != NULL) { - g_free(codecname); - } + if (entry->bytes_before <= byte) { + last_entry = entry; + } + } while (entry->bytes_before <= byte); - return caps; + return last_entry; } -static void -gst_avi_demux_strf_vids (GstAviDemux *avi_demux) +static gst_avi_index_entry * +gst_avi_demux_index_entry_for_frame (GstAviDemux *avi, + gint stream_nr, + guint32 frame, + guint32 flags) { - gst_riff_strf_vids *strf; - gst_riff_strh *strh; - guint8 *strfdata; - GstPad *srcpad; - GstCaps *caps = NULL; - avi_stream_context *stream; - GstByteStream *bs = avi_demux->bs; - guint32 got_bytes; - gchar *padname; + gst_avi_index_entry *entry = NULL, *last_entry = NULL; + gint i; - got_bytes = gst_bytestream_peek_bytes (bs, &strfdata, sizeof (gst_riff_strf_vids)); - strf = (gst_riff_strf_vids *) strfdata; - if (got_bytes != sizeof (gst_riff_strf_vids)) - return; + i = -1; + do { + entry = gst_avi_demux_index_next (avi, stream_nr, i + 1, flags); + if (!entry) + return NULL; - padname = g_strdup_printf ("video_%02d", avi_demux->num_v_streams); - srcpad = gst_pad_new_from_template (videosrctempl, padname); - g_free (padname); + i = entry->index_nr; - /* let's try some gstreamer-like mime-type caps */ - strh = &avi_demux->stream[avi_demux->num_streams].strh; - caps = gst_avi_demux_video_caps (GUINT32_FROM_LE(strf->compression), - strh, strf, avi_demux); + if (entry->frames_before <= frame) { + last_entry = entry; + } + } while (entry->frames_before <= frame); - if (caps != NULL) { - gst_pad_try_set_caps (srcpad, caps); - } - gst_pad_set_formats_function (srcpad, gst_avi_demux_get_src_formats); - gst_pad_set_event_mask_function (srcpad, gst_avi_demux_get_event_mask); - gst_pad_set_event_function (srcpad, gst_avi_demux_handle_src_event); - gst_pad_set_query_type_function (srcpad, gst_avi_demux_get_src_query_types); - gst_pad_set_query_function (srcpad, gst_avi_demux_handle_src_query); - gst_pad_set_convert_function (srcpad, gst_avi_demux_src_convert); - - stream = &avi_demux->stream[avi_demux->num_streams]; - stream->pad = srcpad; - gst_pad_set_element_private (srcpad, stream); - avi_demux->num_streams++; - avi_demux->num_v_streams++; - - gst_element_add_pad (GST_ELEMENT (avi_demux), srcpad); + return last_entry; } -#ifdef G_HAVE_ISO_VARARGS - -#define GST_AVI_AUD_CAPS_NEW(name, mimetype, ...) \ - (strf != NULL) ? \ - GST_CAPS_NEW (name, \ - mimetype, \ - "rate", GST_PROPS_INT (rate), \ - "channels", GST_PROPS_INT (channels), \ - __VA_ARGS__) \ - : \ - GST_CAPS_NEW (name, \ - mimetype, \ - "rate", GST_PROPS_INT_RANGE (8000, 96000), \ - "channels", GST_PROPS_INT_RANGE (1, 2), \ - __VA_ARGS__) - - -#elif defined(G_HAVE_GNUC_VARARGS) - -#define GST_AVI_AUD_CAPS_NEW(name, mimetype, props...) \ - (strf != NULL) ? \ - GST_CAPS_NEW (name, \ - mimetype, \ - "rate", GST_PROPS_INT (rate), \ - "channels", GST_PROPS_INT (channels), \ - ##props) \ - : \ - GST_CAPS_NEW (name, \ - mimetype, \ - "rate", GST_PROPS_INT_RANGE (8000, 96000), \ - "channels", GST_PROPS_INT_RANGE (1, 2), \ - ##props) -#endif - -static GstCaps * -gst_avi_demux_audio_caps (guint16 codec_id, - gst_riff_strf_auds *strf, - GstAviDemux *avi_demux) +static const GstFormat * +gst_avi_demux_get_src_formats (GstPad *pad) { - GstCaps *caps = NULL; - gchar *codecname = NULL; - gint rate = -1, channels = -1; - - if (strf != NULL) { - rate = GUINT32_FROM_LE (strf->rate); - channels = GUINT16_FROM_LE (strf->channels); - } + avi_stream_context *stream = gst_pad_get_element_private (pad); - switch (codec_id) { - case GST_RIFF_WAVE_FORMAT_MPEGL3: /* mp3 */ - caps = GST_AVI_AUD_CAPS_NEW ("avi_demux_audio_src_mp3", - "audio/mpeg", - "layer", GST_PROPS_INT (3)); - codecname = g_strdup_printf("MPEG-1 layer 3 audio (0x%04x)", - codec_id); - break; + static const GstFormat src_a_formats[] = { + GST_FORMAT_TIME, + GST_FORMAT_BYTES, + GST_FORMAT_DEFAULT, + 0 + }; + static const GstFormat src_v_formats[] = { + GST_FORMAT_TIME, + GST_FORMAT_DEFAULT, + 0 + }; - case GST_RIFF_WAVE_FORMAT_MPEGL12: /* mp1 or mp2 */ - caps = GST_AVI_AUD_CAPS_NEW ("avi_demux_audio_src_mp12", - "audio/mpeg", - "layer", GST_PROPS_INT (2)); - codecname = g_strdup_printf("MPEG-1 layer 1/2 audio (0x%04x)", - codec_id); - break; + return (stream->strh->type == GST_RIFF_FCC_auds ? + src_a_formats : src_v_formats); +} - case GST_RIFF_WAVE_FORMAT_PCM: /* PCM/wav */ { - GstPropsEntry *width = NULL, *depth = NULL, *signedness = NULL; +static gboolean +gst_avi_demux_src_convert (GstPad *pad, + GstFormat src_format, + gint64 src_value, + GstFormat *dest_format, + gint64 *dest_value) +{ + gboolean res = TRUE; + /*GstAviDemux *avi = GST_AVI_DEMUX (gst_pad_get_parent (pad));*/ + avi_stream_context *stream = gst_pad_get_element_private (pad); - if (strf != NULL) { - gint ba = GUINT16_FROM_LE (strf->blockalign); - gint ch = GUINT16_FROM_LE (strf->channels); - gint ws = GUINT16_FROM_LE (strf->size); + if (stream->strh->type != GST_RIFF_FCC_auds && + (src_format == GST_FORMAT_BYTES || + *dest_format == GST_FORMAT_BYTES)) + return FALSE; - width = gst_props_entry_new ("width", - GST_PROPS_INT (ba * 8 / ch)); - depth = gst_props_entry_new ("depth", - GST_PROPS_INT (ws)); - signedness = gst_props_entry_new ("signed", - GST_PROPS_BOOLEAN (ws != 8)); - } else { - signedness = gst_props_entry_new ("signed", - GST_PROPS_LIST ( - GST_PROPS_BOOLEAN (TRUE), - GST_PROPS_BOOLEAN (FALSE))); - width = gst_props_entry_new ("width", - GST_PROPS_LIST ( - GST_PROPS_INT (8), - GST_PROPS_INT (16))); - depth = gst_props_entry_new ("depth", - GST_PROPS_LIST ( - GST_PROPS_INT (8), - GST_PROPS_INT (16))); + switch (src_format) { + case GST_FORMAT_TIME: + switch (*dest_format) { + case GST_FORMAT_BYTES: + *dest_value = src_value * stream->strh->rate / + (stream->strh->scale * GST_SECOND); + break; + case GST_FORMAT_DEFAULT: + *dest_value = src_value * stream->strh->rate / + (stream->strh->scale * GST_SECOND); + break; + default: + res = FALSE; + break; } - - caps = GST_AVI_AUD_CAPS_NEW ("avi_demux_audio_src_pcm", - "audio/x-raw-int", - "endianness", - GST_PROPS_INT (G_LITTLE_ENDIAN)); - gst_props_add_entry (caps->properties, width); - gst_props_add_entry (caps->properties, depth); - gst_props_add_entry (caps->properties, signedness); - - codecname = g_strdup_printf("Raw PCM/WAV (0x%04x)", - codec_id); - } break; - - case GST_RIFF_WAVE_FORMAT_MULAW: - if (strf != NULL && strf->size != 8) { - g_warning ("invalid depth (%d) of mulaw audio, overwriting.", - strf->size); + case GST_FORMAT_BYTES: + switch (*dest_format) { + case GST_FORMAT_TIME: + *dest_value = ((gfloat) src_value) * GST_SECOND / stream->strh->rate; + break; + default: + res = FALSE; + break; } - caps = GST_AVI_AUD_CAPS_NEW ("avidemux_audio_src", - "audio/x-mulaw", - NULL); - codecname = g_strdup_printf("A-law encoded (0x%04x)", - codec_id); break; - - case GST_RIFF_WAVE_FORMAT_ALAW: - if (strf != NULL && strf->size != 8) { - g_warning ("invalid depth (%d) of alaw audio, overwriting.", - strf->size); + case GST_FORMAT_DEFAULT: + switch (*dest_format) { + case GST_FORMAT_TIME: + *dest_value = ((((gfloat) src_value) * stream->strh->scale) / + stream->strh->rate) * GST_SECOND; + break; + default: + res = FALSE; + break; } - caps = GST_AVI_AUD_CAPS_NEW ("avidemux_audio_src", - "audio/x-alaw", - NULL); - codecname = g_strdup_printf("A-law encoded (0x%04x)", - codec_id); - break; - - case GST_RIFF_WAVE_FORMAT_VORBIS1: /* ogg/vorbis mode 1 */ - case GST_RIFF_WAVE_FORMAT_VORBIS2: /* ogg/vorbis mode 2 */ - case GST_RIFF_WAVE_FORMAT_VORBIS3: /* ogg/vorbis mode 3 */ - case GST_RIFF_WAVE_FORMAT_VORBIS1PLUS: /* ogg/vorbis mode 1+ */ - case GST_RIFF_WAVE_FORMAT_VORBIS2PLUS: /* ogg/vorbis mode 2+ */ - case GST_RIFF_WAVE_FORMAT_VORBIS3PLUS: /* ogg/vorbis mode 3+ */ - caps = GST_AVI_AUD_CAPS_NEW ("asf_demux_audio_src_vorbis", - "audio/x-vorbis", - NULL); - codecname = g_strdup_printf("Vorbis (0x%04x)", - codec_id); - break; - - case GST_RIFF_WAVE_FORMAT_A52: - caps = GST_AVI_AUD_CAPS_NEW ("asf_demux_audio_src_ac3", - "audio/x-ac3", - NULL); - codecname = g_strdup_printf("AC-3 (0x%04x)", - codec_id); break; - default: - g_warning ("avidemux: unkown audio format 0x%04x", - codec_id); - break; - } - - if (avi_demux != NULL && codecname != NULL) { - /* set audio codec in streaminfo */ - GstPropsEntry *entry; - entry = gst_props_entry_new("audiocodec", - GST_PROPS_STRING(codecname)); - gst_props_add_entry(avi_demux->streaminfo->properties, entry); - } - if (codecname != NULL) { - g_free (codecname); - } - - return caps; -} - -static void -gst_avi_demux_strf_auds (GstAviDemux *avi_demux) -{ - gst_riff_strf_auds *strf; - guint8 *strfdata; - GstPad *srcpad; - GstCaps *caps = NULL; - avi_stream_context *stream; - GstByteStream *bs = avi_demux->bs; - guint32 got_bytes; - gchar *padname; - - got_bytes = gst_bytestream_peek_bytes (bs, &strfdata, sizeof (gst_riff_strf_auds)); - strf = (gst_riff_strf_auds *) strfdata; - if (got_bytes != sizeof (gst_riff_strf_auds)) - return; - - GST_INFO ( "gst_avi_demux: strf tag found in context auds"); - GST_INFO ( "gst_avi_demux: format %d", GUINT16_FROM_LE (strf->format)); - GST_INFO ( "gst_avi_demux: channels %d", GUINT16_FROM_LE (strf->channels)); - GST_INFO ( "gst_avi_demux: rate %d", GUINT32_FROM_LE (strf->rate)); - GST_INFO ( "gst_avi_demux: av_bps %d", GUINT32_FROM_LE (strf->av_bps)); - GST_INFO ( "gst_avi_demux: blockalign %d", GUINT16_FROM_LE (strf->blockalign)); - GST_INFO ( "gst_avi_demux: size %d", GUINT16_FROM_LE (strf->size)); - - padname = g_strdup_printf ("audio_%02d", - avi_demux->num_a_streams); - srcpad = gst_pad_new_from_template (audiosrctempl, padname); - g_free (padname); - - caps = gst_avi_demux_audio_caps (GUINT16_FROM_LE (strf->format), - strf, avi_demux); - - if (caps != NULL) { - gst_pad_try_set_caps(srcpad, caps); + res = FALSE; } - gst_pad_set_formats_function (srcpad, gst_avi_demux_get_src_formats); - gst_pad_set_event_mask_function (srcpad, gst_avi_demux_get_event_mask); - gst_pad_set_event_function (srcpad, gst_avi_demux_handle_src_event); - gst_pad_set_query_type_function (srcpad, gst_avi_demux_get_src_query_types); - gst_pad_set_query_function (srcpad, gst_avi_demux_handle_src_query); - gst_pad_set_convert_function (srcpad, gst_avi_demux_src_convert); - - stream = &avi_demux->stream[avi_demux->num_streams]; - stream->pad = srcpad; - gst_pad_set_element_private (srcpad, stream); - avi_demux->num_streams++; - avi_demux->num_a_streams++; - - gst_element_add_pad (GST_ELEMENT (avi_demux), srcpad); -} -static GstCaps * -gst_avi_demux_iavs_caps (void) -{ - return GST_CAPS_NEW ("avi_type_dv", - "video/x-dv", - "systemstream", GST_PROPS_BOOLEAN (TRUE)); + return res; } -static void -gst_avi_demux_strf_iavs (GstAviDemux *avi_demux) +static const GstQueryType * +gst_avi_demux_get_src_query_types (GstPad *pad) { - gst_riff_strf_iavs *strf; - guint8 *strfdata; - GstPad *srcpad; - GstCaps *caps = NULL; - avi_stream_context *stream; - GstByteStream *bs = avi_demux->bs; - guint32 got_bytes; - gchar *padname; - GstPropsEntry *entry; - - got_bytes = gst_bytestream_peek_bytes (bs, &strfdata, sizeof (gst_riff_strf_iavs)); - strf = (gst_riff_strf_iavs *) strfdata; - if (got_bytes != sizeof (gst_riff_strf_iavs)) - return; - - GST_INFO ( "gst_avi_demux: strf tag found in context iavs"); - GST_INFO ( "gst_avi_demux: DVAAuxSrc %08x", GUINT32_FROM_LE (strf->DVAAuxSrc)); - GST_INFO ( "gst_avi_demux: DVAAuxCtl %08x", GUINT32_FROM_LE (strf->DVAAuxCtl)); - GST_INFO ( "gst_avi_demux: DVAAuxSrc1 %08x", GUINT32_FROM_LE (strf->DVAAuxSrc1)); - GST_INFO ( "gst_avi_demux: DVAAuxCtl1 %08x", GUINT32_FROM_LE (strf->DVAAuxCtl1)); - GST_INFO ( "gst_avi_demux: DVVAuxSrc %08x", GUINT32_FROM_LE (strf->DVVAuxSrc)); - GST_INFO ( "gst_avi_demux: DVVAuxCtl %08x", GUINT32_FROM_LE (strf->DVVAuxCtl)); - GST_INFO ( "gst_avi_demux: DVReserved1 %08x", GUINT32_FROM_LE (strf->DVReserved1)); - GST_INFO ( "gst_avi_demux: DVReserved2 %08x", GUINT32_FROM_LE (strf->DVReserved2)); - - padname = g_strdup_printf ("video_%02d", - avi_demux->num_v_streams); - srcpad = gst_pad_new_from_template (videosrctempl, padname); - g_free (padname); - - caps = gst_avi_demux_iavs_caps (); - entry = gst_props_entry_new("videocodec", - GST_PROPS_STRING("Digital Video type 1")); - gst_props_add_entry(avi_demux->streaminfo->properties, entry); - - if (caps != NULL) { - gst_pad_try_set_caps(srcpad, caps); - } - gst_pad_set_formats_function (srcpad, gst_avi_demux_get_src_formats); - gst_pad_set_event_mask_function (srcpad, gst_avi_demux_get_event_mask); - gst_pad_set_event_function (srcpad, gst_avi_demux_handle_src_event); - gst_pad_set_query_type_function (srcpad, gst_avi_demux_get_src_query_types); - gst_pad_set_query_function (srcpad, gst_avi_demux_handle_src_query); - gst_pad_set_convert_function (srcpad, gst_avi_demux_src_convert); - - stream = &avi_demux->stream[avi_demux->num_streams]; - stream->pad = srcpad; - gst_pad_set_element_private (srcpad, stream); - avi_demux->num_streams++; - avi_demux->num_v_streams++; - - gst_element_add_pad (GST_ELEMENT (avi_demux), srcpad); -} + static const GstQueryType src_types[] = { + GST_QUERY_TOTAL, + GST_QUERY_POSITION, + 0 + }; -static void -gst_avi_debug_entry (const gchar *prefix, gst_avi_index_entry *entry) -{ - GST_DEBUG ("%s: %05d %d %08llx %05d %14" G_GINT64_FORMAT " %08x %08x (%d) %08x", - prefix, entry->index_nr, entry->stream_nr, - (unsigned long long)entry->bytes_before, - entry->frames_before, entry->ts, entry->flags, entry->offset, - entry->offset, entry->size); + return src_types; } -static void -gst_avi_demux_parse_index (GstAviDemux *avi_demux, - gulong filepos, gulong offset) +static gboolean +gst_avi_demux_handle_src_query (GstPad *pad, + GstQueryType type, + GstFormat *format, + gint64 *value) { - GstBuffer *buf; - gulong index_size; - guint32 got_bytes; - gint i; - gst_riff_index_entry *entry; - guint32 id; - - if (!gst_bytestream_seek (avi_demux->bs, filepos + offset, GST_SEEK_METHOD_SET)) { - GST_INFO ( "avidemux: could not seek to index"); - return; - } - do { - guint32 remaining; - GstEvent *event; - - got_bytes = gst_bytestream_read (avi_demux->bs, &buf, 8); - if (got_bytes == 8) - break; - - gst_bytestream_get_status (avi_demux->bs, &remaining, &event); - gst_event_unref (event); - } while (TRUE); - - if (GST_BUFFER_OFFSET (buf) != filepos + offset || GST_BUFFER_SIZE (buf) != 8) { - GST_INFO ( "avidemux: could not get index, got %" G_GINT64_FORMAT " %d, expected %ld", - GST_BUFFER_OFFSET (buf), GST_BUFFER_SIZE (buf), filepos + offset); - goto end; - } - - id = GUINT32_FROM_LE (*(guint32 *)GST_BUFFER_DATA (buf)); - - if (id != GST_RIFF_TAG_idx1) { - GST_INFO ( "avidemux: no index found"); - goto end; - } - - index_size = GUINT32_FROM_LE(*(guint32 *)(GST_BUFFER_DATA (buf) + 4)); - gst_buffer_unref (buf); - - gst_bytestream_size_hint (avi_demux->bs, index_size); - - got_bytes = gst_bytestream_read (avi_demux->bs, &buf, index_size); - if (got_bytes < index_size) { - GST_INFO ( "avidemux: error reading index"); - goto end; - } - - avi_demux->index_size = index_size/sizeof(gst_riff_index_entry); - GST_INFO ( "avidemux: index size %lu", avi_demux->index_size); - - avi_demux->index_entries = g_malloc (avi_demux->index_size * sizeof (gst_avi_index_entry)); - - entry = (gst_riff_index_entry *) GST_BUFFER_DATA (buf); - - for (i = 0; i < avi_demux->index_size; i++) { - avi_stream_context *stream; - gint stream_nr; - gst_avi_index_entry *target = &avi_demux->index_entries[i]; - GstFormat format; - guint32 id; - - id = GUINT32_FROM_LE (entry[i].id); - stream_nr = CHUNKID_TO_STREAMNR (id); - if (stream_nr > avi_demux->num_streams || stream_nr < 0) { - avi_demux->index_entries[i].stream_nr = -1; - continue; - } - - target->stream_nr = stream_nr; - stream = &avi_demux->stream[stream_nr]; - - target->index_nr = i; - target->flags = GUINT32_FROM_LE (entry[i].flags); - target->size = GUINT32_FROM_LE (entry[i].size); - target->offset = GUINT32_FROM_LE (entry[i].offset); - - /* figure out if the index is 0 based or relative to the MOVI start */ - if (i == 0) { - if (target->offset < filepos) - avi_demux->index_offset = filepos - 4; - else - avi_demux->index_offset = 0; - } - - target->bytes_before = stream->total_bytes; - target->frames_before = stream->total_frames; - - format = GST_FORMAT_TIME; - if (stream->strh.type == GST_RIFF_FCC_auds) { - /* all audio frames are keyframes */ - target->flags |= GST_RIFF_IF_KEYFRAME; - } - - /* constant rate stream */ - if (stream->strh.samplesize && stream->strh.type == GST_RIFF_FCC_auds) { - gst_pad_convert (stream->pad, GST_FORMAT_BYTES, stream->total_bytes, - &format, &target->ts); - } - /* VBR stream */ - else { - gst_pad_convert (stream->pad, GST_FORMAT_DEFAULT, stream->total_frames, - &format, &target->ts); - } - gst_avi_debug_entry ("index", target); - - stream->total_bytes += target->size; - stream->total_frames++; - } - for (i = 0; i < avi_demux->num_streams; i++) { - avi_stream_context *stream; - - stream = &avi_demux->stream[i]; - GST_DEBUG ("stream %i: %d frames, %" G_GINT64_FORMAT " bytes", - i, stream->total_frames, stream->total_bytes); - } - gst_buffer_unref (buf); - -end: - GST_DEBUG ("index offset at %08lx", filepos); - - if (!gst_bytestream_seek (avi_demux->bs, filepos, GST_SEEK_METHOD_SET)) { - GST_INFO ( "avidemux: could not seek back to movi"); - return; - } -} - -static gst_avi_index_entry* -gst_avi_demux_index_next (GstAviDemux *avi_demux, gint stream_nr, gint start, guint32 flags) -{ - gint i; - gst_avi_index_entry *entry = NULL; - - for (i = start; i < avi_demux->index_size; i++) { - entry = &avi_demux->index_entries[i]; - - if (entry->stream_nr == stream_nr && (entry->flags & flags) == flags) { - break; - } - } - - return entry; -} - -static gst_avi_index_entry* -gst_avi_demux_index_entry_for_time (GstAviDemux *avi_demux, gint stream_nr, guint64 time, guint32 flags) -{ - gst_avi_index_entry *entry = NULL, *last_entry = NULL; - gint i; - - i = -1; - do { - entry = gst_avi_demux_index_next (avi_demux, stream_nr, i + 1, flags); - if (!entry) - return NULL; - - i = entry->index_nr; - - if (entry->ts <= time) { - last_entry = entry; - } - } - while (entry->ts <= time); - - return last_entry; -} - -static const GstFormat* -gst_avi_demux_get_src_formats (GstPad *pad) -{ - avi_stream_context *stream = gst_pad_get_element_private (pad); - - static const GstFormat src_a_formats[] = { - GST_FORMAT_TIME, - GST_FORMAT_BYTES, - GST_FORMAT_DEFAULT, - 0 - }; - static const GstFormat src_v_formats[] = { - GST_FORMAT_TIME, - GST_FORMAT_DEFAULT, - 0 - }; - - return (stream->strh.type == GST_RIFF_FCC_auds ? src_a_formats : src_v_formats); -} - -static gboolean -gst_avi_demux_src_convert (GstPad *pad, GstFormat src_format, gint64 src_value, - GstFormat *dest_format, gint64 *dest_value) -{ - gboolean res = TRUE; - avi_stream_context *stream = gst_pad_get_element_private (pad); - - if (stream->strh.type != GST_RIFF_FCC_auds && - (src_format == GST_FORMAT_BYTES || *dest_format == GST_FORMAT_BYTES)) - return FALSE; - - switch (src_format) { - case GST_FORMAT_TIME: - switch (*dest_format) { - case GST_FORMAT_BYTES: - *dest_value = src_value * stream->strh.rate / (stream->strh.scale * GST_SECOND); - break; - case GST_FORMAT_DEFAULT: - *dest_value = src_value * stream->strh.rate / (stream->strh.scale * GST_SECOND); - break; - default: - res = FALSE; - break; - } - break; - case GST_FORMAT_BYTES: - switch (*dest_format) { - case GST_FORMAT_TIME: - *dest_value = ((gfloat)src_value) * GST_SECOND / stream->strh.rate; - break; - default: - res = FALSE; - break; - } - break; - case GST_FORMAT_DEFAULT: - switch (*dest_format) { - case GST_FORMAT_TIME: - *dest_value = ((((gfloat)src_value) * stream->strh.scale) / stream->strh.rate) * GST_SECOND; - break; - default: - res = FALSE; - break; - } - break; - default: - res = FALSE; - } - - return res; -} - -static const GstQueryType* -gst_avi_demux_get_src_query_types (GstPad *pad) -{ - static const GstQueryType src_types[] = { - GST_QUERY_TOTAL, - GST_QUERY_POSITION, - 0 - }; - - return src_types; -} - -static gboolean -gst_avi_demux_handle_src_query (GstPad *pad, GstQueryType type, - GstFormat *format, gint64 *value) -{ - gboolean res = TRUE; - //GstAviDemux *avi_demux = GST_AVI_DEMUX (gst_pad_get_parent (pad)); - avi_stream_context *stream = gst_pad_get_element_private (pad); + gboolean res = TRUE; + /*GstAviDemux *avi = GST_AVI_DEMUX (gst_pad_get_parent (pad));*/ + avi_stream_context *stream = gst_pad_get_element_private (pad); switch (type) { case GST_QUERY_TOTAL: switch (*format) { case GST_FORMAT_TIME: - *value = (((gfloat)stream->strh.scale) * stream->strh.length / stream->strh.rate) * GST_SECOND; + *value = (((gfloat) stream->strh->scale) * stream->strh->length / + stream->strh->rate) * GST_SECOND; break; case GST_FORMAT_BYTES: - if (stream->strh.type == GST_RIFF_FCC_auds) { + if (stream->strh->type == GST_RIFF_FCC_auds) { *value = stream->total_bytes; } else res = FALSE; break; case GST_FORMAT_DEFAULT: - if (stream->strh.type == GST_RIFF_FCC_auds) - *value = stream->strh.length * stream->strh.samplesize; - else if (stream->strh.type == GST_RIFF_FCC_vids) - *value = stream->strh.length; + if (stream->strh->type == GST_RIFF_FCC_auds) + *value = stream->strh->length * stream->strh->samplesize; + else if (stream->strh->type == GST_RIFF_FCC_vids) + *value = stream->strh->length; else res = FALSE; break; @@ -1411,20 +474,23 @@ gst_avi_demux_handle_src_query (GstPad *pad, GstQueryType type, case GST_QUERY_POSITION: switch (*format) { case GST_FORMAT_TIME: - if (stream->strh.samplesize && stream->strh.type == GST_RIFF_FCC_auds) { - //*value = (((gfloat)stream->current_byte) * stream->strh.scale / stream->strh.rate) * GST_SECOND; - *value = ((gfloat)stream->current_byte) * GST_SECOND / stream->strh.rate; + if (stream->strh->samplesize && + stream->strh->type == GST_RIFF_FCC_auds) { + *value = ((gfloat) stream->current_byte) * GST_SECOND / + stream->strh->rate; } else { - *value = (((gfloat)stream->current_frame) * stream->strh.scale / stream->strh.rate) * GST_SECOND; + *value = (((gfloat) stream->current_frame) * stream->strh->scale / + stream->strh->rate) * GST_SECOND; } break; case GST_FORMAT_BYTES: *value = stream->current_byte; break; case GST_FORMAT_DEFAULT: - if (stream->strh.samplesize && stream->strh.type == GST_RIFF_FCC_auds) - *value = stream->current_byte * stream->strh.samplesize; + if (stream->strh->samplesize && + stream->strh->type == GST_RIFF_FCC_auds) + *value = stream->current_byte * stream->strh->samplesize; else *value = stream->current_frame; break; @@ -1442,22 +508,22 @@ gst_avi_demux_handle_src_query (GstPad *pad, GstQueryType type, } static gint32 -gst_avi_demux_sync_streams (GstAviDemux *avi_demux, guint64 time) +gst_avi_demux_sync_streams (GstAviDemux *avi, + guint64 time) { gint i; guint32 min_index = G_MAXUINT; avi_stream_context *stream; gst_avi_index_entry *entry; - for (i = 0; i < avi_demux->num_streams; i++) { - stream = &avi_demux->stream[i]; + for (i = 0; i < avi->num_streams; i++) { + stream = &avi->stream[i]; GST_DEBUG ("finding %d for time %" G_GINT64_FORMAT, i, time); - entry = gst_avi_demux_index_entry_for_time (avi_demux, stream->num, time, GST_RIFF_IF_KEYFRAME); + entry = gst_avi_demux_index_entry_for_time (avi, stream->num, time, + GST_RIFF_IF_KEYFRAME); if (entry) { - gst_avi_debug_entry ("sync entry", entry); - min_index = MIN (entry->index_nr, min_index); } } @@ -1465,15 +531,16 @@ gst_avi_demux_sync_streams (GstAviDemux *avi_demux, guint64 time) /* now we know the entry we need to sync on. calculate number of frames to * skip fro there on and the stream stats */ - for (i = 0; i < avi_demux->num_streams; i++) { + for (i = 0; i < avi->num_streams; i++) { gst_avi_index_entry *next_entry; - stream = &avi_demux->stream[i]; + stream = &avi->stream[i]; /* next entry */ - next_entry = gst_avi_demux_index_next (avi_demux, stream->num, min_index, 0); + next_entry = gst_avi_demux_index_next (avi, stream->num, + min_index, 0); /* next entry with keyframe */ - entry = gst_avi_demux_index_next (avi_demux, stream->num, min_index, GST_RIFF_IF_KEYFRAME); - gst_avi_debug_entry ("final sync", entry); + entry = gst_avi_demux_index_next (avi, stream->num, min_index, + GST_RIFF_IF_KEYFRAME); stream->current_byte = next_entry->bytes_before; stream->current_frame = next_entry->frames_before; @@ -1481,13 +548,15 @@ gst_avi_demux_sync_streams (GstAviDemux *avi_demux, guint64 time) GST_DEBUG ("%d skip %d", stream->num, stream->skip); } + GST_DEBUG ("final index at %d", min_index); return min_index; } static gboolean -gst_avi_demux_send_event (GstElement *element, GstEvent *event) +gst_avi_demux_send_event (GstElement *element, + GstEvent *event) { const GList *pads; @@ -1502,6 +571,7 @@ gst_avi_demux_send_event (GstElement *element, GstEvent *event) gst_event_ref (event); if (gst_avi_demux_handle_src_event (pad, event)) { gst_event_unref (event); + return TRUE; } } @@ -1510,15 +580,15 @@ gst_avi_demux_send_event (GstElement *element, GstEvent *event) } gst_event_unref (event); + return FALSE; } -static const GstEventMask* +static const GstEventMask * gst_avi_demux_get_event_mask (GstPad *pad) { static const GstEventMask masks[] = { { GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_KEY_UNIT }, - { GST_EVENT_SEEK_SEGMENT, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_KEY_UNIT }, { 0, } }; @@ -1526,54 +596,65 @@ gst_avi_demux_get_event_mask (GstPad *pad) } static gboolean -gst_avi_demux_handle_src_event (GstPad *pad, GstEvent *event) +gst_avi_demux_handle_src_event (GstPad *pad, + GstEvent *event) { gboolean res = TRUE; - GstAviDemux *avi_demux = GST_AVI_DEMUX (gst_pad_get_parent (pad)); + GstAviDemux *avi = GST_AVI_DEMUX (gst_pad_get_parent (pad)); avi_stream_context *stream; stream = gst_pad_get_element_private (pad); switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_SEEK_SEGMENT: - stream->end_pos = GST_EVENT_SEEK_ENDOFFSET (event); case GST_EVENT_SEEK: - GST_DEBUG ("seek format %d, %08x", GST_EVENT_SEEK_FORMAT (event), stream->strh.type); + GST_DEBUG ("seek format %d, %08x", GST_EVENT_SEEK_FORMAT (event), + stream->strh->type); + switch (GST_EVENT_SEEK_FORMAT (event)) { case GST_FORMAT_BYTES: case GST_FORMAT_DEFAULT: - break; - case GST_FORMAT_TIME: - { - gst_avi_index_entry *seek_entry, *entry; + case GST_FORMAT_TIME: { + gst_avi_index_entry *seek_entry, *entry = NULL; gint64 desired_offset = GST_EVENT_SEEK_OFFSET (event); guint32 flags; guint64 min_index; - /* no seek on audio yet */ - if (stream->strh.type == GST_RIFF_FCC_auds) { + if (stream->strh->type == GST_RIFF_FCC_auds) { res = FALSE; goto done; } GST_DEBUG ("seeking to %" G_GINT64_FORMAT, desired_offset); flags = GST_RIFF_IF_KEYFRAME; + switch (GST_EVENT_SEEK_FORMAT (event)) { + case GST_FORMAT_BYTES: + entry = gst_avi_demux_index_entry_for_byte (avi, stream->num, + desired_offset, + flags); + break; + case GST_FORMAT_DEFAULT: + entry = gst_avi_demux_index_entry_for_frame (avi, stream->num, + desired_offset, + flags); + break; + case GST_FORMAT_TIME: + entry = gst_avi_demux_index_entry_for_time (avi, stream->num, + desired_offset, + flags); + break; + } - entry = gst_avi_demux_index_entry_for_time (avi_demux, stream->num, desired_offset, GST_RIFF_IF_KEYFRAME); if (entry) { - desired_offset = entry->ts; - min_index = gst_avi_demux_sync_streams (avi_demux, desired_offset); - seek_entry = &avi_demux->index_entries[min_index]; - - gst_avi_debug_entry ("syncing to entry", seek_entry); - - avi_demux->seek_offset = seek_entry->offset + avi_demux->index_offset; - avi_demux->seek_pending = TRUE; - avi_demux->last_seek = seek_entry->ts; - } - else { - GST_DEBUG ("no index entry found for time %" G_GINT64_FORMAT, desired_offset); + min_index = gst_avi_demux_sync_streams (avi, entry->ts); + seek_entry = &avi->index_entries[min_index]; + + avi->seek_offset = seek_entry->offset + avi->index_offset; + avi->last_seek = entry->ts; + } else { + GST_DEBUG ("no index entry found for format=%d value=%" + G_GINT64_FORMAT, GST_EVENT_SEEK_FORMAT (event), + desired_offset); res = FALSE; } break; @@ -1594,408 +675,851 @@ done: return res; } -static gboolean -gst_avi_demux_handle_sink_event (GstAviDemux *avi_demux) +/* + * "Open" a RIFF file. + */ + +gboolean +gst_avi_demux_stream_init (GstAviDemux *avi) { - guint32 remaining; - GstEvent *event; - GstEventType type; - gboolean res = TRUE; - - gst_bytestream_get_status (avi_demux->bs, &remaining, &event); + GstRiffRead *riff = GST_RIFF_READ (avi); + guint32 doctype; - type = event? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN; - GST_DEBUG ("avidemux: event %p %d", event, type); + if (!gst_riff_read_header (riff, &doctype)) + return FALSE; + if (doctype != GST_RIFF_RIFF_AVI) { + gst_element_error (GST_ELEMENT (avi), "Not an AVI file"); + return FALSE; + } - switch (type) { - case GST_EVENT_EOS: - gst_bytestream_flush (avi_demux->bs, remaining); - gst_pad_event_default (avi_demux->sinkpad, event); - res = FALSE; - goto done; - case GST_EVENT_FLUSH: - g_warning ("flush event"); + return TRUE; +} + +/* + * Read 'avih' header. + */ + +gboolean +gst_avi_demux_stream_avih (GstAviDemux *avi, + guint32 *flags, + guint32 *streams) +{ + GstRiffRead *riff = GST_RIFF_READ (avi); + guint32 tag; + GstBuffer *buf; + gst_riff_avih *avih; + + if (!gst_riff_read_data (riff, &tag, &buf)) + return FALSE; + + if (tag != GST_RIFF_TAG_avih) { + g_warning ("Not a avih chunk"); + gst_buffer_unref (buf); + return FALSE; + } + if (GST_BUFFER_SIZE (buf) < sizeof (gst_riff_avih)) { + g_warning ("Too small avih (%d available, %d needed)", + GST_BUFFER_SIZE (buf), sizeof (gst_riff_avih)); + gst_buffer_unref (buf); + return FALSE; + } + + avih = (gst_riff_avih *) GST_BUFFER_DATA (buf); + +#if (G_BYTE_ORDER == G_BIG_ENDIAN) + avih->us_frame = GUINT32_FROM_LE (avih->us_frame); + avih->max_bps = GUINT32_FROM_LE (avih->max_bps); + avih->pad_gran = GUINT32_FROM_LE (avih->pad_gran); + avih->flags = GUINT32_FROM_LE (avih->flags); + avih->tot_frames = GUINT32_FROM_LE (avih->tot_frames); + avih->init_frames = GUINT32_FROM_LE (avih->init_frames); + avih->streams = GUINT32_FROM_LE (avih->streams); + avih->bufsize = GUINT32_FROM_LE (avih->bufsize); + avih->width = GUINT32_FROM_LE (avih->width); + avih->height = GUINT32_FROM_LE (avih->height); + avih->scale = GUINT32_FROM_LE (avih->scale); + avih->rate = GUINT32_FROM_LE (avih->rate); + avih->start = GUINT32_FROM_LE (avih->start); + avih->length = GUINT32_FROM_LE (avih->length); +#endif + + /* debug stuff */ + GST_INFO ("avih tag found:"); + GST_INFO (" us_frame %u", avih->us_frame); + GST_INFO (" max_bps %u", avih->max_bps); + GST_INFO (" pad_gran %u", avih->pad_gran); + GST_INFO (" flags 0x%08x", avih->flags); + GST_INFO (" tot_frames %u", avih->tot_frames); + GST_INFO (" init_frames %u", avih->init_frames); + GST_INFO (" streams %u", avih->streams); + GST_INFO (" bufsize %u", avih->bufsize); + GST_INFO (" width %u", avih->width); + GST_INFO (" height %u", avih->height); + GST_INFO (" scale %u", avih->scale); + GST_INFO (" rate %u", avih->rate); + GST_INFO (" start %u", avih->start); + GST_INFO (" length %u", avih->length); + + avi->num_frames = avih->tot_frames; + avi->us_per_frame = avih->us_frame; + *streams = avih->streams; + *flags = avih->flags; + + gst_buffer_unref (buf); + + return TRUE; +} + +/* + * Add a stream. + */ + +static gboolean +gst_avi_demux_add_stream (GstAviDemux *avi) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS (avi); + GstRiffRead *riff = GST_RIFF_READ (avi); + guint32 tag; + gst_riff_strh *strh; + gchar *name = NULL, *padname = NULL; + GstCaps *caps = NULL; + GstPadTemplate *templ = NULL; + GstPad *pad; + avi_stream_context *stream; + union { + gst_riff_strf_vids *vids; + gst_riff_strf_auds *auds; + gst_riff_strf_iavs *iavs; + } strf; + + /* the stream starts with a 'strh' header */ + if (!(tag = gst_riff_peek_tag (riff, NULL))) + return FALSE; + if (tag != GST_RIFF_TAG_strh) { + g_warning ("Invalid stream header (no strh at begin)"); + goto skip_stream; + } + if (!gst_riff_read_strh (riff, &strh)) + return FALSE; + + /* then comes a 'strf' of that specific type */ + if (!(tag = gst_riff_peek_tag (riff, NULL))) + return FALSE; + if (tag != GST_RIFF_TAG_strf) { + gst_element_error (GST_ELEMENT (avi), + "Invalid AVI header (no strf as second tag)"); + goto skip_stream; + } + switch (strh->type) { + case GST_RIFF_FCC_vids: + if (!gst_riff_read_strf_vids (riff, &strf.vids)) + return FALSE; + break; + case GST_RIFF_FCC_auds: + if (!gst_riff_read_strf_auds (riff, &strf.auds)) + return FALSE; + break; + case GST_RIFF_FCC_iavs: + if (!gst_riff_read_strf_iavs (riff, &strf.iavs)) + return FALSE; break; - case GST_EVENT_DISCONTINUOUS: - { - gint i; - GstEvent *discont; + default: + g_warning ("Unknown stream type " GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (strh->type)); + goto skip_stream; + } - for (i = 0; i < avi_demux->num_streams; i++) { - avi_stream_context *stream = &avi_demux->stream[i]; + /* read other things */ + while (TRUE) { + if (!(tag = gst_riff_peek_tag (riff, &avi->level_up))) + return FALSE; + else if (avi->level_up) { + avi->level_up--; + break; + } - if (GST_PAD_IS_USABLE (stream->pad)) { - GST_DEBUG ("sending discont on %d %" G_GINT64_FORMAT " + %" G_GINT64_FORMAT " = %" G_GINT64_FORMAT, - i, avi_demux->last_seek, stream->delay, avi_demux->last_seek + stream->delay); + switch (tag) { + case GST_RIFF_TAG_strn: + if (name) + g_free (name); + if (!gst_riff_read_ascii (riff, &tag, &name)) + return FALSE; + break; - discont = gst_event_new_discontinuous (FALSE, GST_FORMAT_TIME, - avi_demux->last_seek + stream->delay , NULL); + default: + GST_WARNING ("Unknown tag " GST_FOURCC_FORMAT " in AVI header", + GST_FOURCC_ARGS (tag)); + /* fall-through */ + + case GST_RIFF_TAG_strd: /* what is this? */ + case GST_RIFF_TAG_JUNK: + if (!gst_riff_read_skip (riff)) + return FALSE; + break; + } - gst_pad_push (stream->pad, GST_DATA (discont)); - } - } + if (avi->level_up) { + avi->level_up--; break; } + } + + /* create stream name + pad */ + switch (strh->type) { + case GST_RIFF_FCC_vids: + padname = g_strdup_printf ("video_%02d", avi->num_v_streams); + templ = gst_element_class_get_pad_template (klass, "video_%02d"); + caps = gst_riff_create_video_caps (strf.vids->compression, strh, strf.vids); + g_free (strf.vids); + avi->num_v_streams++; + break; + case GST_RIFF_FCC_auds: + padname = g_strdup_printf ("audio_%02d", avi->num_a_streams); + templ = gst_element_class_get_pad_template (klass, "audio_%02d"); + caps = gst_riff_create_audio_caps (strf.auds->format, strh, strf.auds); + g_free (strf.auds); + avi->num_a_streams++; + break; + case GST_RIFF_FCC_iavs: + padname = g_strdup_printf ("video_%02d", avi->num_v_streams); + templ = gst_element_class_get_pad_template (klass, "video_%02d"); + caps = gst_riff_create_iavs_caps (strh->fcc_handler, strh, strf.iavs); + g_free (strf.iavs); + avi->num_v_streams++; + break; default: - g_warning ("unhandled event %d", type); + g_assert (0); + } + + /* set proper settings and add it */ + pad = gst_pad_new_from_template (templ, padname); + g_free (padname); + if (caps != NULL) + gst_pad_try_set_caps (pad, caps); + + gst_pad_set_formats_function (pad, gst_avi_demux_get_src_formats); + gst_pad_set_event_mask_function (pad, gst_avi_demux_get_event_mask); + gst_pad_set_event_function (pad, gst_avi_demux_handle_src_event); + gst_pad_set_query_type_function (pad, gst_avi_demux_get_src_query_types); + gst_pad_set_query_function (pad, gst_avi_demux_handle_src_query); + gst_pad_set_convert_function (pad, gst_avi_demux_src_convert); + + stream = &avi->stream[avi->num_streams]; + stream->pad = pad; + stream->strh = strh; + stream->num = avi->num_streams; + stream->delay = 0LL; + stream->total_bytes = 0LL; + stream->total_frames = 0; + stream->current_frame = 0; + stream->current_byte = 0; + stream->current_entry = -1; + stream->skip = 0; + gst_pad_set_element_private (pad, stream); + avi->num_streams++; + + gst_element_add_pad (GST_ELEMENT (avi), pad); + + return TRUE; + +skip_stream: + while (TRUE) { + if (!(tag = gst_riff_peek_tag (riff, &avi->level_up))) + return FALSE; + if (avi->level_up) { + avi->level_up--; + break; + } + if (!gst_riff_read_skip (riff)) + return FALSE; + } + + /* add a "NULL" stream */ + avi->num_streams++; + + return TRUE; /* recoverable */ +} + +/* + * Read an openDML-2.0 extension header. + */ + +static gboolean +gst_avi_demux_stream_odml (GstAviDemux *avi) +{ + GstRiffRead *riff = GST_RIFF_READ (avi); + guint32 tag; + + /* read contents */ + while (TRUE) { + if (!(tag = gst_riff_peek_tag (riff, &avi->level_up))) + return FALSE; + else if (avi->level_up) { + avi->level_up--; + break; + } + + switch (tag) { + case GST_RIFF_TAG_dmlh: { + gst_riff_dmlh *dmlh; + GstBuffer *buf; + + if (!gst_riff_read_data (riff, &tag, &buf)) + return FALSE; + if (GST_BUFFER_SIZE (buf) < sizeof (gst_riff_dmlh)) { + g_warning ("DMLH entry is too small (%d bytes, %d needed)", + GST_BUFFER_SIZE (buf), sizeof (gst_riff_dmlh)); + gst_buffer_unref (buf); + break; + } + dmlh = (gst_riff_dmlh *) GST_BUFFER_DATA (buf); + +#if (G_BYTE_ORDER == G_BIG_ENDIAN) + dmlh->totalframes = GUINT32_FROM_LE (dmlh->totalframes); +#endif + + GST_INFO ("dmlh tag found:"); + GST_INFO (" totalframes: %u", dmlh->totalframes); + + avi->num_frames = dmlh->totalframes; + gst_buffer_unref (buf); + break; + } + + default: + GST_WARNING ("Unknown tag " GST_FOURCC_FORMAT " in AVI header", + GST_FOURCC_ARGS (tag)); + /* fall-through */ + + case GST_RIFF_TAG_JUNK: + if (!gst_riff_read_skip (riff)) + return FALSE; + break; + } + + if (avi->level_up) { + avi->level_up--; break; + } + } + + return TRUE; +} + +/* + * Seek to index, read it, seek back. + */ + +gboolean +gst_avi_demux_stream_index (GstAviDemux *avi) +{ + GstBuffer *buf = NULL; + guint i; + GstEvent *event; + GstRiffRead *riff = GST_RIFF_READ (avi); + guint64 pos_before, pos_after, length; + guint32 tag; + + /* first, we need to know the current position (to seek back + * when we're done) and the total length of the file. */ + length = gst_bytestream_length (riff->bs); + pos_before = gst_bytestream_tell (riff->bs); + + /* skip movi */ + if (!gst_riff_read_skip (riff)) + return FALSE; + + /* assure that we've got data left */ + pos_after = gst_bytestream_tell (riff->bs); + if (pos_after + 8 > length) { + g_warning ("File said that it has an index, but there is no index data!"); + goto end; + } + + /* assure that it's an index */ + if (!(tag = gst_riff_peek_tag (riff, NULL))) + return FALSE; + if (tag != GST_RIFF_TAG_idx1) { + g_warning ("No index after data, but " GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (tag)); + goto end; + } + + /* read index */ + if (!gst_riff_read_data (riff, &tag, &buf)) + return FALSE; + + /* parse all entries */ + avi->index_size = GST_BUFFER_SIZE (buf) / sizeof (gst_riff_index_entry); + avi->index_entries = g_malloc (avi->index_size * sizeof (gst_avi_index_entry)); + GST_INFO ("%u index entries", avi->index_size); + + for (i = 0; i < avi->index_size; i++) { + gst_riff_index_entry *entry; + avi_stream_context *stream; + gint stream_nr; + gst_avi_index_entry *target; + GstFormat format; + + entry = &((gst_riff_index_entry *) GST_BUFFER_DATA (buf))[i]; + entry->id = GUINT32_FROM_LE (entry->id); + entry->offset = GUINT32_FROM_LE (entry->offset); + entry->flags = GUINT32_FROM_LE (entry->flags); + entry->size = GUINT32_FROM_LE (entry->size); + target = &avi->index_entries[i]; + + stream_nr = CHUNKID_TO_STREAMNR (entry->id); + if (stream_nr >= avi->num_streams || stream_nr < 0) { + g_warning ("Index entry %d has invalid stream nr %d", + i, stream_nr); + target->stream_nr = -1; + continue; + } + target->stream_nr = stream_nr; + stream = &avi->stream[stream_nr]; + + target->index_nr = i; + target->flags = entry->flags; + target->size = entry->size; + target->offset = entry->offset; + + /* figure out if the index is 0 based or relative to the MOVI start */ + if (i == 0) { + if (target->offset < pos_before) + avi->index_offset = pos_before + 8; + else + avi->index_offset = 0; + } + + target->bytes_before = stream->total_bytes; + target->frames_before = stream->total_frames; + + format = GST_FORMAT_TIME; + if (stream->strh->type == GST_RIFF_FCC_auds) { + /* all audio frames are keyframes */ + target->flags |= GST_RIFF_IF_KEYFRAME; + } + + if (stream->strh->samplesize && stream->strh->type == GST_RIFF_FCC_auds) { + /* constant rate stream */ + gst_pad_convert (stream->pad, GST_FORMAT_BYTES, + stream->total_bytes, &format, &target->ts); + } else { + /* VBR stream */ + gst_pad_convert (stream->pad, GST_FORMAT_DEFAULT, + stream->total_frames, &format, &target->ts); + } + + stream->total_bytes += target->size; + stream->total_frames++; + } + + /* debug our indexes */ + for (i = 0; i < avi->num_streams; i++) { + avi_stream_context *stream; + + stream = &avi->stream[i]; + GST_DEBUG ("stream %u: %u frames, %" G_GINT64_FORMAT " bytes", + i, stream->total_frames, stream->total_bytes); } +end: + if (buf) + gst_buffer_unref (buf); + + /* seek back to the data */ + if (!(event = gst_riff_read_seek (riff, pos_before))) + return FALSE; gst_event_unref (event); -done: + return TRUE; +} - return res; +/* + * Scan the file for all chunks to "create" a new index. + */ + +gboolean +gst_avi_demux_stream_scan (GstAviDemux *avi) +{ + //GstRiffRead *riff = GST_RIFF_READ (avi); + + /* FIXME */ + + return TRUE; } +/* + * Read full AVI headers. + */ -static void -gst_avi_demux_loop (GstElement *element) +gboolean +gst_avi_demux_stream_header (GstAviDemux *avi) { - GstAviDemux *avi_demux; - gst_riff_riff chunk; - guint32 flush = 0; - guint32 got_bytes; - GstByteStream *bs; - guint64 pos; - - avi_demux = GST_AVI_DEMUX (element); - - bs = avi_demux->bs; - - if (avi_demux->seek_pending) { - GST_DEBUG ("avidemux: seek pending to %" G_GINT64_FORMAT " %08llx", - avi_demux->seek_offset, (unsigned long long)avi_demux->seek_offset); - - if (!gst_bytestream_seek (avi_demux->bs, - avi_demux->seek_offset, - GST_SEEK_METHOD_SET)) - { - GST_INFO ( "avidemux: could not seek"); + GstRiffRead *riff = GST_RIFF_READ (avi); + guint32 tag, flags, streams; + + /* the header consists of a 'hdrl' LIST tag */ + if (!(tag = gst_riff_peek_tag (riff, NULL))) + return FALSE; + if (tag != GST_RIFF_TAG_LIST) { + gst_element_error (GST_ELEMENT (avi), + "Invalid AVI header (no LIST at start): " + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag)); + return FALSE; + } + if (!gst_riff_read_list (riff, &tag)) + return FALSE; + if (tag != GST_RIFF_LIST_hdrl) { + gst_element_error (GST_ELEMENT (avi), + "Invalid AVI header (no hdrl at start): " + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag)); + return FALSE; + } + + /* the hdrl starts with a 'avih' header */ + if (!(tag = gst_riff_peek_tag (riff, NULL))) + return FALSE; + if (tag != GST_RIFF_TAG_avih) { + gst_element_error (GST_ELEMENT (avi), + "Invalid AVI header (no avih at start): " + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (tag)); + return FALSE; + } + if (!gst_avi_demux_stream_avih (avi, &flags, &streams)) + return FALSE; + + /* now, read the elements from the header until the end */ + while (TRUE) { + if (!(tag = gst_riff_peek_tag (riff, &avi->level_up))) + return FALSE; + else if (avi->level_up) { + avi->level_up--; + break; + } + + switch (tag) { + case GST_RIFF_TAG_LIST: + if (!(tag = gst_riff_peek_list (riff))) + return FALSE; + + switch (tag) { + case GST_RIFF_LIST_strl: + if (!gst_riff_read_list (riff, &tag) || + !gst_avi_demux_add_stream (avi)) + return FALSE; + break; + + case GST_RIFF_LIST_odml: + if (!gst_riff_read_list (riff, &tag) || + !gst_avi_demux_stream_odml (avi)) + return FALSE; + break; + + case GST_RIFF_LIST_INFO: + if (!gst_riff_read_list (riff, &tag) || + !gst_riff_read_info (riff)) + return FALSE; + break; + + default: + GST_WARNING ("Unknown list " GST_FOURCC_FORMAT " in AVI header", + GST_FOURCC_ARGS (tag)); + /* fall-through */ + + case GST_RIFF_TAG_JUNK: + if (!gst_riff_read_skip (riff)) + return FALSE; + break; + } + + break; + + default: + GST_WARNING ("Unknown tag " GST_FOURCC_FORMAT " in AVI header", + GST_FOURCC_ARGS (tag)); + /* fall-through */ + + case GST_RIFF_TAG_JUNK: + if (!gst_riff_read_skip (riff)) + return FALSE; + break; + } + + if (avi->level_up) { + avi->level_up--; + break; } - avi_demux->seek_pending = FALSE; } - pos = gst_bytestream_tell (bs); - do { - gst_riff_riff *temp_chunk; - guint8 *tempdata; - guint32 skipsize; - - /* read first two dwords to get chunktype and size */ - while (TRUE) { - got_bytes = gst_bytestream_peek_bytes (bs, &tempdata, sizeof (gst_riff_chunk)); - temp_chunk = (gst_riff_riff *) tempdata; - if (got_bytes < sizeof (gst_riff_chunk)) { - if (!gst_avi_demux_handle_sink_event (avi_demux)) - return; - } - else break; + if (avi->num_streams != streams) { + g_warning ("Stream header mentioned %d streams, but %d available", + streams, avi->num_streams); + } + + /* we've got streaminfo now */ + g_object_notify (G_OBJECT(avi), "streaminfo"); + + /* Now, find the data (i.e. skip all junk between header and data) */ + while (1) { + if (!(tag = gst_riff_peek_tag (riff, NULL))) + return FALSE; + if (tag != GST_RIFF_TAG_LIST) { + if (!gst_riff_read_skip (riff)) + return FALSE; + continue; + } + if (!(tag = gst_riff_peek_list (riff))) + return FALSE; + if (tag != GST_RIFF_LIST_movi) { + if (!gst_riff_read_skip (riff)) + return FALSE; + continue; } + break; + } + + /* create or read stream index (for seeking) */ + if (flags & GST_RIFF_AVIH_HASINDEX) { + if (!gst_avi_demux_stream_index (avi)) + return FALSE; + } else { + if (!gst_avi_demux_stream_scan (avi)) + return FALSE; + } + + return TRUE; +} + +/* + * Handle seek. + */ + +static gboolean +gst_avi_demux_handle_seek (GstAviDemux *avi) +{ + GstRiffRead *riff = GST_RIFF_READ (avi); + guint i; + GstEvent *event; + + /* FIXME: if we seek in an openDML file, we will have multiple + * primary levels. Seeking in between those will cause havoc. */ - chunk.id = GUINT32_FROM_LE (temp_chunk->id); - chunk.size = GUINT32_FROM_LE (temp_chunk->size); + if (!(event = gst_riff_read_seek (riff, avi->seek_offset))) + return FALSE; + gst_event_unref (event); + + for (i = 0; i < avi->num_streams; i++) { + avi_stream_context *stream = &avi->stream[i]; + + if (GST_PAD_IS_USABLE (stream->pad)) { + event = gst_event_new_discontinuous (FALSE, GST_FORMAT_TIME, + avi->last_seek + stream->delay , NULL); + gst_pad_push (stream->pad, GST_DATA (event)); + } + } - switch (chunk.id) { - case GST_RIFF_TAG_RIFF: + return TRUE; +} + +/* + * Read data. + */ + +gboolean +gst_avi_demux_stream_data (GstAviDemux *avi) +{ + GstRiffRead *riff = GST_RIFF_READ (avi); + guint32 tag; + guint stream_nr; + gst_avi_index_entry *entry; + + if (avi->seek_offset != (guint64) -1) { + if (!gst_avi_demux_handle_seek (avi)) + return FALSE; + avi->seek_offset = (guint64) -1; + } + + /* peek first (for the end of this 'list/movi' section) */ + if (!(tag = gst_riff_peek_tag (riff, &avi->level_up))) + return FALSE; + + /* if we're at top-level, we didn't read the 'movi' + * list tag yet. This can also be 'AVIX' in case of + * openDML-2.0 AVI files. Lastly, it might be idx1, + * in which case we skip it so we come at EOS. */ + while (g_list_length (riff->level) < 2) { + if (!(tag = gst_riff_peek_tag (riff, NULL))) + return FALSE; + + switch (tag) { case GST_RIFF_TAG_LIST: - /* read complete list chunk */ - while (TRUE) { - got_bytes = gst_bytestream_peek_bytes (bs, &tempdata, sizeof (gst_riff_list)); - temp_chunk = (gst_riff_riff *) tempdata; - if (got_bytes < sizeof (gst_riff_list)) { - if (!gst_avi_demux_handle_sink_event (avi_demux)) - return; - } - else break; + if (!(tag = gst_riff_peek_list (riff))) + return FALSE; + + switch (tag) { + case GST_RIFF_LIST_AVIX: + case GST_RIFF_LIST_movi: + if (!gst_riff_read_list (riff, &tag)) + return FALSE; + /* we're now going to read buffers! */ + break; + + default: + GST_WARNING ("Unknown list " GST_FOURCC_FORMAT " before AVI data", + GST_FOURCC_ARGS (tag)); + /* fall-through */ + + case GST_RIFF_TAG_JUNK: + if (!gst_riff_read_skip (riff)) + return FALSE; + break; } - chunk.type = GUINT32_FROM_LE (temp_chunk->type); - skipsize = sizeof (gst_riff_list); + break; + default: - skipsize = sizeof (gst_riff_chunk); + GST_WARNING ("Unknown tag " GST_FOURCC_FORMAT " before AVI data", + GST_FOURCC_ARGS (tag)); + /* fall-through */ + + case GST_RIFF_TAG_idx1: + case GST_RIFF_TAG_JUNK: + if (!gst_riff_read_skip (riff)) + return FALSE; break; } - gst_bytestream_flush_fast (bs, skipsize); - } - while (FALSE); + } - /* need to flush an even number of bytes at the end */ - flush = (chunk.size + 1) & ~1; + /* And then, we get the data */ + if (!(tag = gst_riff_peek_tag (riff, NULL))) + return FALSE; + stream_nr = CHUNKID_TO_STREAMNR (tag); + if (stream_nr < 0 || stream_nr >= avi->num_streams) { + /* recoverable */ + g_warning ("Invalid stream ID %d (" GST_FOURCC_FORMAT ")", + stream_nr, GST_FOURCC_ARGS (tag)); + if (!gst_riff_read_skip (riff)) + return FALSE; + } else { + avi_stream_context *stream; + GstClockTime next_ts; + GstFormat format; + GstBuffer *buf; - switch (avi_demux->state) { - case GST_AVI_DEMUX_START: - if (chunk.id != GST_RIFF_TAG_RIFF && - chunk.type != GST_RIFF_RIFF_AVI) { - gst_element_error (element, "This doesn't appear to be an AVI file %08x %08x", chunk.id, chunk.type); - return; - } - avi_demux->state = GST_AVI_DEMUX_HEADER; - /* we are not going to flush lists */ - flush = 0; - break; - case GST_AVI_DEMUX_HEADER: - GST_DEBUG ("riff tag: %4.4s %08x", (gchar *)&chunk.id, chunk.size); - switch (chunk.id) { - case GST_RIFF_TAG_LIST: - GST_DEBUG ("list type: %4.4s", (gchar *)&chunk.type); - switch (chunk.type) { - case GST_RIFF_LIST_movi: - { - guint64 filepos; - - filepos = gst_bytestream_tell (bs); - - gst_avi_demux_parse_index (avi_demux, filepos , chunk.size - 4); - - if (avi_demux->avih.bufsize) { - gst_bytestream_size_hint (avi_demux->bs, avi_demux->avih.bufsize); - } - - avi_demux->state = GST_AVI_DEMUX_MOVI; - /* and tell the bastards that we have stream info too */ - gst_props_debug(avi_demux->streaminfo->properties); - g_object_notify(G_OBJECT(avi_demux), "streaminfo"); - break; - } - case GST_RIFF_LIST_INFO: - gst_avi_demux_metadata (avi_demux, chunk.size); - break; - default: - break; - } - flush = 0; - break; - case GST_RIFF_TAG_avih: - gst_avi_demux_avih (avi_demux); - break; - case GST_RIFF_TAG_strh: - gst_avi_demux_strh (avi_demux); - break; - case GST_RIFF_TAG_strf: - switch (avi_demux->fcc_type) { - case GST_RIFF_FCC_vids: - gst_avi_demux_strf_vids (avi_demux); - break; - case GST_RIFF_FCC_auds: - gst_avi_demux_strf_auds (avi_demux); - break; - case GST_RIFF_FCC_iavs: - gst_avi_demux_strf_iavs (avi_demux); - break; - case GST_RIFF_FCC_pads: - case GST_RIFF_FCC_txts: - default: - GST_INFO ( "gst_avi_demux_chain: strh type %s not supported", - gst_riff_id_to_fourcc (avi_demux->fcc_type)); - break; - } - break; - case GST_RIFF_TAG_strn: - gst_avi_demux_strn (avi_demux, chunk.size); - break; - case GST_RIFF_TAG_dmlh: - gst_avi_demux_dmlh (avi_demux); - break; - case GST_RIFF_TAG_JUNK: - case GST_RIFF_ISFT: - break; - default: - GST_DEBUG (" ***** unknown chunkid %08x", chunk.id); - break; + /* get buffer */ + if (!gst_riff_read_data (riff, &tag, &buf)) + return FALSE; + + /* get time of this buffer */ + stream = &avi->stream[stream_nr]; + entry = gst_avi_demux_index_next (avi, stream_nr, + stream->current_entry + 1, 0); + if (entry) { + stream->current_entry = entry->index_nr; + if (entry->flags & GST_RIFF_IF_KEYFRAME) { + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_KEY_UNIT); } - break; - case GST_AVI_DEMUX_MOVI: - switch (chunk.id) { - case GST_RIFF_00dc: - case GST_RIFF_00db: - case GST_RIFF_00__: - case GST_RIFF_01wb: - { - gint stream_id; - avi_stream_context *stream; - gint64 next_ts; - GstFormat format; - - stream_id = CHUNKID_TO_STREAMNR (chunk.id); - - stream = &avi_demux->stream[stream_id]; - - GST_LOG_OBJECT (avi_demux, "gst_avi_demux_chain: tag found %08x size %08x stream_id %d", - chunk.id, chunk.size, stream_id); - - format = GST_FORMAT_TIME; - gst_pad_query (stream->pad, GST_QUERY_POSITION, &format, &next_ts); - - if (stream->strh.init_frames == stream->current_frame && stream->delay == 0) - stream->delay = next_ts; - - stream->current_frame++; - stream->current_byte += chunk.size; - - if (stream->skip) { - stream->skip--; - } - else { - if (GST_PAD_IS_USABLE (stream->pad)) { - if (next_ts >= stream->end_pos) { - gst_pad_push (stream->pad, GST_DATA (gst_event_new (GST_EVENT_EOS))); - GST_DEBUG ("end stream %d: %" G_GINT64_FORMAT " %d %" G_GINT64_FORMAT, - stream_id, next_ts, stream->current_frame - 1, - stream->end_pos); - } - else { - GstBuffer *buf; - guint32 got_bytes; - - if (chunk.size) { - GstClockTime dur_ts; - got_bytes = gst_bytestream_peek (avi_demux->bs, &buf, chunk.size); - - GST_BUFFER_TIMESTAMP (buf) = next_ts; - - gst_pad_query (stream->pad, GST_QUERY_POSITION, &format, &dur_ts); - GST_BUFFER_DURATION (buf) = dur_ts - next_ts; - - if (stream->need_flush) { - /* FIXME, do some flush event here */ - stream->need_flush = FALSE; - } - GST_LOG_OBJECT (avi_demux, "send stream %d: %" - G_GINT64_FORMAT " %d %" G_GINT64_FORMAT " %08x", - stream_id, next_ts, stream->current_frame - 1, - stream->delay, chunk.size); - - gst_pad_push(stream->pad, GST_DATA (buf)); - } - } - } - } - break; - } - default: - GST_DEBUG (" ***** unknown chunkid %08x", chunk.id); - break; + } + format = GST_FORMAT_TIME; + gst_pad_query (stream->pad, GST_QUERY_POSITION, + &format, &next_ts); + + /* set delay (if any) */ + if (stream->strh->init_frames == stream->current_frame && + stream->delay == 0) + stream->delay = next_ts; + + stream->current_frame++; + stream->current_byte += GST_BUFFER_SIZE (buf); + + /* should we skip this data? */ + if (stream->skip) { + stream->skip--; + gst_buffer_unref (buf); + } else { + if (!stream->pad || !GST_PAD_IS_USABLE (stream->pad)) { + gst_buffer_unref (buf); + } else { + GstClockTime dur_ts; + + GST_BUFFER_TIMESTAMP (buf) = next_ts; + gst_pad_query (stream->pad, GST_QUERY_POSITION, + &format, &dur_ts); + GST_BUFFER_DURATION (buf) = dur_ts - next_ts; + + gst_pad_push (stream->pad, GST_DATA (buf)); } - break; + } } - while (flush) { - gboolean res; - - res = gst_bytestream_flush (avi_demux->bs, flush); - if (!res) { - guint32 remaining; - GstEvent *event; + return TRUE; +} - gst_bytestream_get_status (avi_demux->bs, &remaining, &event); - gst_event_unref (event); - } - else +static void +gst_avi_demux_loop (GstElement *element) +{ + GstAviDemux *avi = GST_AVI_DEMUX (element); + + switch (avi->state) { + case GST_AVI_DEMUX_START: + if (!gst_avi_demux_stream_init (avi)) + return; + avi->state = GST_AVI_DEMUX_HEADER; + /* fall-through */ + + case GST_AVI_DEMUX_HEADER: + if (!gst_avi_demux_stream_header (avi)) + return; + avi->state = GST_AVI_DEMUX_MOVI; + /* fall-through */ + + case GST_AVI_DEMUX_MOVI: + if (!gst_avi_demux_stream_data (avi)) + return; break; + + default: + g_assert (0); } } static GstElementStateReturn gst_avi_demux_change_state (GstElement *element) { - GstAviDemux *avi_demux = GST_AVI_DEMUX (element); + GstAviDemux *avi = GST_AVI_DEMUX (element); switch (GST_STATE_TRANSITION (element)) { - case GST_STATE_NULL_TO_READY: - break; case GST_STATE_READY_TO_PAUSED: - avi_demux->bs = gst_bytestream_new (avi_demux->sinkpad); - avi_demux->last_seek = 0; - avi_demux->state = GST_AVI_DEMUX_START; - avi_demux->num_streams = 0; - avi_demux->num_v_streams = 0; - avi_demux->num_a_streams = 0; - avi_demux->index_entries = NULL; - avi_demux->index_size = 0; - avi_demux->seek_pending = 0; - avi_demux->metadata = NULL; - gst_avi_demux_streaminfo(avi_demux); - break; - case GST_STATE_PAUSED_TO_PLAYING: - break; - case GST_STATE_PLAYING_TO_PAUSED: + gst_avi_demux_streaminfo (avi); break; case GST_STATE_PAUSED_TO_READY: - gst_bytestream_destroy (avi_demux->bs); - gst_caps_replace (&avi_demux->metadata, NULL); - gst_caps_replace (&avi_demux->streaminfo, NULL); - break; - case GST_STATE_READY_TO_NULL: + gst_avi_demux_reset (avi); break; default: break; } - parent_class->change_state (element); + if (GST_ELEMENT_CLASS (parent_class)->change_state) + return GST_ELEMENT_CLASS (parent_class)->change_state (element); return GST_STATE_SUCCESS; } static void -gst_avi_demux_get_property (GObject *object, guint prop_id, GValue *value, +gst_avi_demux_get_property (GObject *object, + guint prop_id, + GValue *value, GParamSpec *pspec) { - GstAviDemux *src; - - g_return_if_fail (GST_IS_AVI_DEMUX (object)); - - src = GST_AVI_DEMUX (object); + GstAviDemux *avi = GST_AVI_DEMUX (object); switch (prop_id) { - case ARG_BITRATE: - break; - case ARG_METADATA: - g_value_set_boxed(value, src->metadata); - break; case ARG_STREAMINFO: - g_value_set_boxed(value, src->streaminfo); + g_value_set_boxed (value, avi->streaminfo); break; default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } - -static gboolean -plugin_init (GstPlugin *plugin) -{ - if (!gst_library_load ("gstbytestream")) - return FALSE; - if (!gst_library_load ("gstriff")) - return FALSE; - if (!gst_library_load("gstvideo")) - return FALSE; - - GST_DEBUG_CATEGORY_INIT (avidemux_debug, "avidemux", 0, "Demuxer for AVI video"); - - if (!gst_element_register (plugin, "avidemux", GST_RANK_PRIMARY, - GST_TYPE_AVI_DEMUX)) { - return FALSE; - } - - if (!gst_element_register (plugin, "avimux", GST_RANK_PRIMARY, - GST_TYPE_AVIMUX)) { - return FALSE; - } - - return TRUE; -} - - -GST_PLUGIN_DEFINE ( - GST_VERSION_MAJOR, - GST_VERSION_MINOR, - "avimux", - "AVI stream handling", - plugin_init, - VERSION, - GST_LICENSE, - GST_PACKAGE, - GST_ORIGIN -) - diff --git a/gst/avi/gstavidemux.h b/gst/avi/gstavidemux.h index a933428..15eec1f 100644 --- a/gst/avi/gstavidemux.h +++ b/gst/avi/gstavidemux.h @@ -17,30 +17,26 @@ * Boston, MA 02111-1307, USA. */ - #ifndef __GST_AVI_DEMUX_H__ #define __GST_AVI_DEMUX_H__ - #include -#include -#include -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ +#include "gst/riff/riff-ids.h" +#include "gst/riff/riff-read.h" + +G_BEGIN_DECLS #define GST_TYPE_AVI_DEMUX \ - (gst_avi_demux_get_type()) + (gst_avi_demux_get_type ()) #define GST_AVI_DEMUX(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AVI_DEMUX,GstAviDemux)) + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_AVI_DEMUX, GstAviDemux)) #define GST_AVI_DEMUX_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AVI_DEMUX,GstAviDemux)) + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_AVI_DEMUX, GstAviDemux)) #define GST_IS_AVI_DEMUX(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AVI_DEMUX)) + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_AVI_DEMUX)) #define GST_IS_AVI_DEMUX_CLASS(obj) \ - (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AVI_DEMUX)) - + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_AVI_DEMUX)) #define GST_AVI_DEMUX_MAX_STREAMS 16 @@ -48,85 +44,85 @@ extern "C" { ((((chunkid) & 0xff) - '0') * 10 + \ (((chunkid) >> 8) & 0xff) - '0') -typedef struct _GstAviDemux GstAviDemux; -typedef struct _GstAviDemuxClass GstAviDemuxClass; - -typedef struct -{ - gint index_nr; - gint stream_nr; - guint64 ts; - guint32 flags; - guint32 offset; - gint size; - guint64 bytes_before; - guint32 frames_before; +typedef struct { + gint index_nr; + gint stream_nr; + guint64 ts; + guint32 flags; + guint32 offset; + gint size; + guint64 bytes_before; + guint32 frames_before; } gst_avi_index_entry; -typedef struct -{ - GstPad *pad; - gint num; - gst_riff_strh strh; - guint32 current_frame; - guint32 current_byte; - guint64 delay; - gboolean need_flush; - guint64 end_pos; +typedef struct { + /* index of this streamcontext */ + guint num; + + /* pad, strh */ + GstPad *pad; + gst_riff_strh *strh; - guint64 total_bytes; - guint32 total_frames; + /* current position (byte, frame, time) */ + guint current_frame; + guint64 current_byte; + gint current_entry; - guint32 skip; + /* delay in time (init_frames) */ + guint64 delay; + /* stream length */ + guint64 total_bytes; + guint32 total_frames; + + guint32 skip; } avi_stream_context; -typedef enum -{ +typedef enum { GST_AVI_DEMUX_START, GST_AVI_DEMUX_HEADER, GST_AVI_DEMUX_MOVI, } GstAviDemuxState; -struct _GstAviDemux { - GstElement element; +typedef struct _GstAviDemux { + GstRiffRead parent; /* pads */ - GstPad *sinkpad, *srcpad; + GstPad *sinkpad; /* AVI decoding state */ - guint32 fcc_type; GstAviDemuxState state; + guint level_up; - GstByteStream *bs; - + /* index */ gst_avi_index_entry *index_entries; - gulong index_size; - gulong index_offset; - - gst_riff_avih avih; + guint index_size; + guint64 index_offset; + /* streams */ guint num_streams; guint num_v_streams; guint num_a_streams; avi_stream_context stream[GST_AVI_DEMUX_MAX_STREAMS]; - gboolean seek_pending; - gint64 seek_offset; - guint64 last_seek; + /* some stream info for length */ + guint32 us_per_frame; + guint32 num_frames; - GstCaps *metadata, *streaminfo; -}; + /* seeking */ + guint64 seek_offset; + guint64 last_seek; -struct _GstAviDemuxClass { - GstElementClass parent_class; -}; + /* info */ + GstCaps *streaminfo; +} GstAviDemux; -GType gst_avi_demux_get_type (void); +typedef struct _GstAviDemuxClass { + GstRiffReadClass parent_class; +} GstAviDemuxClass; -#ifdef __cplusplus -} -#endif /* __cplusplus */ +GType gst_avi_demux_get_type (void); +G_END_DECLS #endif /* __GST_AVI_DEMUX_H__ */ diff --git a/gst/avi/gstavimux.h b/gst/avi/gstavimux.h index 31c64c3..452b722 100644 --- a/gst/avi/gstavimux.h +++ b/gst/avi/gstavimux.h @@ -23,7 +23,8 @@ #include -#include +#include +#include "avi-ids.h" #ifdef __cplusplus diff --git a/gst/matroska/Makefile.am b/gst/matroska/Makefile.am index b0235d8..7efb21a 100644 --- a/gst/matroska/Makefile.am +++ b/gst/matroska/Makefile.am @@ -2,14 +2,18 @@ plugin_LTLIBRARIES = libgstmatroska.la libgstmatroska_la_SOURCES = \ ebml-read.c \ + ebml-write.c \ matroska.c \ - matroska-demux.c + matroska-demux.c \ + matroska-mux.c noinst_HEADERS = \ ebml-ids.h \ ebml-read.h \ + ebml-write.h \ matroska-demux.h \ - matroska-ids.h + matroska-ids.h \ + matroska-mux.h libgstmatroska_la_CFLAGS = $(GST_CFLAGS) libgstmatroska_la_LIBADD = diff --git a/gst/matroska/ebml-read.c b/gst/matroska/ebml-read.c index a6b9225..67b9b5f 100644 --- a/gst/matroska/ebml-read.c +++ b/gst/matroska/ebml-read.c @@ -157,7 +157,20 @@ gst_ebml_read_element_id (GstEbmlRead *ebml, guint32 total; if (gst_bytestream_peek_bytes (ebml->bs, &data, 1) != 1) { - /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/ + GstEvent *event = NULL; + guint32 remaining; + + /* Here, we might encounter EOS */ + gst_bytestream_get_status (ebml->bs, &remaining, &event); + if (event && GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + gst_pad_event_default (ebml->sinkpad, event); + } else { + guint64 pos = gst_bytestream_tell (ebml->bs); + gst_event_unref (event); + gst_element_error (GST_ELEMENT (ebml), + "Read error at position %llu (0x%llx)", + pos, pos); + } return -1; } total = data[0]; @@ -166,13 +179,18 @@ gst_ebml_read_element_id (GstEbmlRead *ebml, len_mask >>= 1; } if (read > 4) { + guint64 pos = gst_bytestream_tell (ebml->bs); gst_element_error (GST_ELEMENT (ebml), - "Invalid EBML ID size tag (0x%x)", data[0]); + "Invalid EBML ID size tag (0x%x) at position %llu (0x%llx)", + data[0], pos, pos); return -1; } if (gst_bytestream_peek_bytes (ebml->bs, &data, read) != read) { - /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/ + guint64 pos = gst_bytestream_tell (ebml->bs); + gst_element_error (GST_ELEMENT (ebml), + "Read error at position %llu (0x%llx)", + pos, pos); return -1; } while (n < read) @@ -201,7 +219,10 @@ gst_ebml_read_element_length (GstEbmlRead *ebml, guint64 total; if (gst_bytestream_peek_bytes (ebml->bs, &data, 1) != 1) { - /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/ + guint64 pos = gst_bytestream_tell (ebml->bs); + gst_element_error (GST_ELEMENT (ebml), + "Read error at position %llu (0x%llx)", + pos, pos); return -1; } total = data[0]; @@ -210,15 +231,20 @@ gst_ebml_read_element_length (GstEbmlRead *ebml, len_mask >>= 1; } if (read > 8) { + guint64 pos = gst_bytestream_tell (ebml->bs); gst_element_error (GST_ELEMENT (ebml), - "Invalid EBML length size tag (0x%x)", data[0]); + "Invalid EBML length size tag (0x%x) at position %llu (0x%llx)", + data[0], pos, pos); return -1; } if ((total &= (len_mask - 1)) == len_mask - 1) num_ffs++; if (gst_bytestream_peek_bytes (ebml->bs, &data, read) != read) { - /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/ + guint64 pos = gst_bytestream_tell (ebml->bs); + gst_element_error (GST_ELEMENT (ebml), + "Read error at position %llu (0x%llx)", + pos, pos); return -1; } while (n < read) { @@ -228,12 +254,6 @@ gst_ebml_read_element_length (GstEbmlRead *ebml, n++; } - if (!total) { - gst_element_error (GST_ELEMENT (ebml), - "Invalid length 0"); - return -1; - } - if (read == num_ffs) *length = G_MAXUINT64; else @@ -254,7 +274,10 @@ gst_ebml_read_element_data (GstEbmlRead *ebml, GstBuffer *buf = NULL; if (gst_bytestream_peek (ebml->bs, &buf, length) != length) { - /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/ + guint64 pos = gst_bytestream_tell (ebml->bs); + gst_element_error (GST_ELEMENT (ebml), + "Read error at position %llu (0x%llx)", + pos, pos); if (buf) gst_buffer_unref (buf); return NULL; @@ -276,16 +299,12 @@ gst_ebml_peek_id (GstEbmlRead *ebml, guint *level_up) { guint32 id; - guint my_level_up; - g_return_val_if_fail (level_up != NULL, 0); + g_assert (level_up); - if (gst_ebml_read_element_id (ebml, &id, &my_level_up) < 0) + if (gst_ebml_read_element_id (ebml, &id, level_up) < 0) return 0; - if (level_up) - *level_up = my_level_up; - return id; } @@ -293,11 +312,47 @@ gst_ebml_peek_id (GstEbmlRead *ebml, * Seek to a given offset. */ -void +GstEvent * gst_ebml_read_seek (GstEbmlRead *ebml, guint64 offset) { - gst_bytestream_seek (ebml->bs, offset, GST_SEEK_METHOD_SET); + guint32 remaining; + GstEvent *event; + guchar *data; + + /* first, flush remaining buffers */ + gst_bytestream_get_status (ebml->bs, &remaining, &event); + if (event) { + g_warning ("Unexpected event before seek"); + gst_event_unref (event); + } + if (remaining) + gst_bytestream_flush_fast (ebml->bs, remaining); + + /* now seek */ + if (!gst_bytestream_seek (ebml->bs, offset, GST_SEEK_METHOD_SET)) { + gst_element_error (GST_ELEMENT (ebml), + "Seek to position %llu (0x%llx) failed", + offset, offset); + return NULL; + } + + /* and now, peek a new byte. This will fail because there's a + * pending event. Then, take the event and return it. */ + if (gst_bytestream_peek_bytes (ebml->bs, &data, 1)) + g_warning ("Unexpected data after seek"); + + /* get the discont event and return */ + gst_bytestream_get_status (ebml->bs, &remaining, &event); + if (!event || GST_EVENT_TYPE (event) != GST_EVENT_DISCONTINUOUS) { + gst_element_error (GST_ELEMENT (ebml), + "No discontinuity event after seek"); + if (event) + gst_event_unref (event); + return NULL; + } + + return event; } /* @@ -308,8 +363,9 @@ gboolean gst_ebml_read_skip (GstEbmlRead *ebml) { gint bytes; - guint32 id; + guint32 id, remaining; guint64 length; + GstEvent *event; if ((bytes = gst_ebml_read_element_id (ebml, &id, NULL)) < 0) return FALSE; @@ -319,7 +375,23 @@ gst_ebml_read_skip (GstEbmlRead *ebml) return FALSE; gst_bytestream_flush_fast (ebml->bs, bytes); - return gst_bytestream_flush (ebml->bs, length); + /* do we have enough bytes left to skip? */ + gst_bytestream_get_status (ebml->bs, &remaining, &event); + if (event) { + g_warning ("Unexpected event before skip"); + gst_event_unref (event); + } + + if (remaining >= length) + return gst_bytestream_flush (ebml->bs, length); + + if (!(event = gst_ebml_read_seek (ebml, + gst_bytestream_tell (ebml->bs) + length))) + return FALSE; + + gst_event_unref (event); + + return TRUE; } /* @@ -365,7 +437,8 @@ gst_ebml_read_uint (GstEbmlRead *ebml, size = GST_BUFFER_SIZE (buf); if (size < 1 || size > 8) { gst_element_error (GST_ELEMENT (ebml), - "Invalid integer element size %d", size); + "Invalid integer element size %d at position %llu (0x%llu)", + size, GST_BUFFER_OFFSET (buf), GST_BUFFER_OFFSET (buf)); gst_buffer_unref (buf); return FALSE; } @@ -400,7 +473,8 @@ gst_ebml_read_sint (GstEbmlRead *ebml, size = GST_BUFFER_SIZE (buf); if (size < 1 || size > 8) { gst_element_error (GST_ELEMENT (ebml), - "Invalid integer element size %d", size); + "Invalid integer element size %d at position %llu (0x%llx)", + size, GST_BUFFER_OFFSET (buf), GST_BUFFER_OFFSET (buf)); gst_buffer_unref (buf); return FALSE; } @@ -439,7 +513,8 @@ gst_ebml_read_float (GstEbmlRead *ebml, if (size != 4 && size != 8 && size != 10) { gst_element_error (GST_ELEMENT (ebml), - "Invalid float element size %d", size); + "Invalid float element size %d at position %llu (0x%llx)", + size, GST_BUFFER_OFFSET (buf), GST_BUFFER_OFFSET (buf)); gst_buffer_unref (buf); return FALSE; } diff --git a/gst/matroska/ebml-read.h b/gst/matroska/ebml-read.h index 52e4e14..d78d13c 100644 --- a/gst/matroska/ebml-read.h +++ b/gst/matroska/ebml-read.h @@ -63,7 +63,7 @@ GType gst_ebml_read_get_type (void); guint32 gst_ebml_peek_id (GstEbmlRead *ebml, guint *level_up); -void gst_ebml_read_seek (GstEbmlRead *ebml, +GstEvent *gst_ebml_read_seek (GstEbmlRead *ebml, guint64 offset); gboolean gst_ebml_read_skip (GstEbmlRead *ebml); gboolean gst_ebml_read_buffer (GstEbmlRead *ebml, @@ -91,7 +91,7 @@ gboolean gst_ebml_read_master (GstEbmlRead *ebml, guint32 *id); gboolean gst_ebml_read_binary (GstEbmlRead *ebml, guint32 *id, - guchar **binary, + guint8 **binary, guint64 *length); gboolean gst_ebml_read_header (GstEbmlRead *read, gchar **doctype, diff --git a/gst/matroska/ebml-write.c b/gst/matroska/ebml-write.c new file mode 100644 index 0000000..9c76895 --- /dev/null +++ b/gst/matroska/ebml-write.c @@ -0,0 +1,580 @@ +/* GStreamer EBML I/O + * (c) 2003 Ronald Bultje + * + * ebml-write.c: write EBML data to file/stream + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "ebml-write.h" +#include "ebml-ids.h" + +enum { + /* FILL ME */ + LAST_SIGNAL +}; + +static void gst_ebml_write_class_init (GstEbmlWriteClass *klass); +static void gst_ebml_write_init (GstEbmlWrite *ebml); +static GstElementStateReturn + gst_ebml_write_change_state (GstElement *element); + +static GstElementClass *parent_class = NULL; + +GType +gst_ebml_write_get_type (void) +{ + static GType gst_ebml_write_type = 0; + + if (!gst_ebml_write_type) { + static const GTypeInfo gst_ebml_write_info = { + sizeof (GstEbmlWriteClass), + NULL, + NULL, + (GClassInitFunc) gst_ebml_write_class_init, + NULL, + NULL, + sizeof (GstEbmlWrite), + 0, + (GInstanceInitFunc) gst_ebml_write_init, + }; + + gst_ebml_write_type = + g_type_register_static (GST_TYPE_ELEMENT, "GstEbmlWrite", + &gst_ebml_write_info, 0); + } + + return gst_ebml_write_type; +} + +static void +gst_ebml_write_class_init (GstEbmlWriteClass *klass) +{ + GstElementClass *gstelement_class = (GstElementClass *) klass; + + parent_class = g_type_class_ref (GST_TYPE_ELEMENT); + + gstelement_class->change_state = gst_ebml_write_change_state; +} + +static void +gst_ebml_write_init (GstEbmlWrite *ebml) +{ + ebml->srcpad = NULL; + ebml->pos = 0; + + ebml->cache = NULL; +} + +static GstElementStateReturn +gst_ebml_write_change_state (GstElement *element) +{ + GstEbmlWrite *ebml = GST_EBML_WRITE (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_PAUSED_TO_READY: + ebml->pos = 0; + break; + default: + break; + } + + if (GST_ELEMENT_CLASS (parent_class)->change_state) + return GST_ELEMENT_CLASS (parent_class)->change_state (element); + + return GST_STATE_SUCCESS; +} + +/* + * Caching. + * + * The idea is that you use this for writing a lot + * of small elements. This will just "queue" all of + * them and they'll be pushed to the next element all + * at once. This saves memory and time for buffer + * allocation and init, and it looks better. + */ + +void +gst_ebml_write_set_cache (GstEbmlWrite *ebml, + guint size) +{ + return; + g_return_if_fail (ebml->cache == NULL); + + ebml->cache = gst_buffer_new_and_alloc (size); + GST_BUFFER_SIZE (ebml->cache) = 0; + GST_BUFFER_OFFSET (ebml->cache) = ebml->pos; + ebml->handled = 0; +} + +void +gst_ebml_write_flush_cache (GstEbmlWrite *ebml) +{ + if (!ebml->cache) + return; + + /* this is very important. It may fail, in which case the client + * programmer didn't use the cache somewhere. That's fatal. */ + g_assert (ebml->handled == GST_BUFFER_SIZE (ebml->cache)); + g_assert (GST_BUFFER_SIZE (ebml->cache) + + GST_BUFFER_OFFSET (ebml->cache) == ebml->pos); + + gst_pad_push (ebml->srcpad, GST_DATA (ebml->cache)); + ebml->cache = NULL; + ebml->handled = 0; +} + +/* + * One-element buffer, in case of no cache. If there is + * a cache, use that instead. + */ + +static GstBuffer * +gst_ebml_write_element_new (GstEbmlWrite *ebml, + guint size) +{ + /* Create new buffer of size + ID + length */ + GstBuffer *buf; + + /* length, ID */ + size += 12; + + /* prefer cache */ + if (ebml->cache) { + if (GST_BUFFER_MAXSIZE (ebml->cache) - + GST_BUFFER_SIZE (ebml->cache) < size) { + GST_LOG ("Cache available, but too small. Clearing..."); + gst_ebml_write_flush_cache (ebml); + } else { + return ebml->cache; + } + } + + /* else, use a one-element buffer. This is slower */ + buf = gst_buffer_new_and_alloc (size); + GST_BUFFER_SIZE (buf) = 0; + + return buf; +} + +/* + * Write element ID into a buffer. + */ + +static void +gst_ebml_write_element_id (GstBuffer *buf, + guint32 id) +{ + guint8 *data = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf); + guint bytes = 4, mask = 0x10; + + /* get ID length */ + while (!(id & (mask << ((bytes - 1) * 8))) && bytes > 0) { + mask <<= 1; + bytes--; + } + + /* if invalid ID, use dummy */ + if (bytes == 0) { + GST_WARNING ("Invalid ID, voiding"); + bytes = 1; + id = GST_EBML_ID_VOID; + } + + /* write out, BE */ + GST_BUFFER_SIZE (buf) += bytes; + while (bytes--) { + data[bytes] = id & 0xff; + id >>= 8; + } +} + +/* + * Write element length into a buffer. + */ + +static void +gst_ebml_write_element_size (GstBuffer *buf, + guint64 size) +{ + guint8 *data = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf); + guint bytes = 1, mask = 0x80; + + /* how many bytes? */ + while ((size >> ((bytes - 1) * 8)) >= mask && bytes <= 8) { + mask >>= 1; + bytes++; + } + + /* if invalid size, use max. */ + if (bytes > 8) { + GST_WARNING ("Invalid size, maximizing"); + mask = 0x01; + bytes = 8; + /* Now here's a real FIXME: we cannot read those yet! */ + size = 0x00ffffffffffffffLLU; + } + + /* write out, BE, with length size marker */ + GST_BUFFER_SIZE (buf) += bytes; + while (bytes-- > 0) { + data[bytes] = size & 0xff; + size >>= 8; + if (!bytes) + *data |= mask; + } +} + +/* + * Write element data into a buffer. + */ + +static void +gst_ebml_write_element_data (GstBuffer *buf, + guint8 *write, + guint64 length) +{ + guint8 *data = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf); + + memcpy (data, write, length); + GST_BUFFER_SIZE (buf) += length; +} + +/* + * Write out buffer by moving it to the next element. + */ + +static void +gst_ebml_write_element_push (GstEbmlWrite *ebml, + GstBuffer *buf) +{ + guint data_size = GST_BUFFER_SIZE (buf) - ebml->handled; + + ebml->pos += data_size; + if (buf == ebml->cache) { + ebml->handled += data_size; + } + + /* if there's no cache, then don't push it! */ + if (ebml->cache) + g_assert (buf == ebml->cache); + else + gst_pad_push (ebml->srcpad, GST_DATA (buf)); +} + +/* + * Seek. + */ + +void +gst_ebml_write_seek (GstEbmlWrite *ebml, + guint64 pos) +{ + GstEvent *seek; + + /* Cache seeking. A bit dangerous, we assume the client writer + * knows what he's doing... */ + if (ebml->cache) { + /* within bounds? */ + if (pos >= GST_BUFFER_OFFSET (ebml->cache) && + pos < GST_BUFFER_OFFSET (ebml->cache) + GST_BUFFER_MAXSIZE (ebml->cache)) { + GST_BUFFER_SIZE (ebml->cache) = pos - GST_BUFFER_OFFSET (ebml->cache); + if (ebml->pos > pos) + ebml->handled -= ebml->pos - pos; + else + ebml->handled += pos - ebml->pos; + ebml->pos = pos; + } else { + GST_LOG ("Seek outside cache range. Clearing..."); + gst_ebml_write_flush_cache (ebml); + } + } + + seek = gst_event_new_seek (GST_FORMAT_BYTES | + GST_SEEK_METHOD_SET, + pos); + gst_pad_push (ebml->srcpad, GST_DATA (seek)); + ebml->pos = pos; +} + +/* + * Get no. bytes needed to write a uint. + */ + +static guint +gst_ebml_write_get_uint_size (guint64 num) +{ + guint size = 1; + + /* get size */ + while (num >= (1LLU << (size * 8)) && size < 8) { + size++; + } + + return size; +} + + +/* + * Write an uint into a buffer. + */ + +static void +gst_ebml_write_set_uint (GstBuffer *buf, + guint64 num, + guint size) +{ + guint8 *data; + + data = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf); + GST_BUFFER_SIZE (buf) += size; + while (size-- > 0) { + data[size] = num & 0xff; + num >>= 8; + } +} + +/* + * Data type wrappers. + */ + +void +gst_ebml_write_uint (GstEbmlWrite *ebml, + guint32 id, + guint64 num) +{ + GstBuffer *buf = gst_ebml_write_element_new (ebml, sizeof (num)); + guint size = gst_ebml_write_get_uint_size (num); + + /* write */ + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, size); + gst_ebml_write_set_uint (buf, num, size); + gst_ebml_write_element_push (ebml, buf); +} + +void +gst_ebml_write_sint (GstEbmlWrite *ebml, + guint32 id, + gint64 num) +{ + GstBuffer *buf = gst_ebml_write_element_new (ebml, sizeof (num)); + /* if the signed number is on the edge of a extra-byte, + * then we'll fall over when detecting it. Example: if I + * have a number (-)0x8000 (G_MINSHORT), then my abs()<<1 + * will be 0x10000; this is G_MAXUSHORT+1! So: if (<0) -1. */ + guint64 unum = (num < 0 ? (-num - 1) << 1 : num << 1); + guint size = gst_ebml_write_get_uint_size (unum); + + /* make unsigned */ + unum = (num < 0 ? -num : num) + (1LLU << ((8 * size) - 1)); + + /* write */ + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, size); + gst_ebml_write_set_uint (buf, unum, size); + gst_ebml_write_element_push (ebml, buf); +} + +void +gst_ebml_write_float (GstEbmlWrite *ebml, + guint32 id, + gdouble num) +{ + gint n; + GstBuffer *buf = gst_ebml_write_element_new (ebml, sizeof (num)); + + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, 8); +#if (G_BYTE_ORDER == G_LITTLE_ENDIAN) + for (n = 0; n < 8; n++) + GST_BUFFER_DATA (buf)[GST_BUFFER_SIZE (buf)] = ((guint8 *) &num)[7-n]; + GST_BUFFER_SIZE (buf) += 8; +#else + gst_ebml_write_element_data (buf, (guint8 *) &num, 8); +#endif + gst_ebml_write_element_push (ebml, buf); +} + +void +gst_ebml_write_ascii (GstEbmlWrite *ebml, + guint32 id, + const gchar *str) +{ + gint len = strlen (str) + 1; /* add trailing '\0' */ + GstBuffer *buf = gst_ebml_write_element_new (ebml, len); + + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, len); + gst_ebml_write_element_data (buf, (guint8 *) str, len); + gst_ebml_write_element_push (ebml, buf); +} + +void +gst_ebml_write_utf8 (GstEbmlWrite *ebml, + guint32 id, + const gchar *str) +{ + gst_ebml_write_ascii (ebml, id, str); +} + +void +gst_ebml_write_date (GstEbmlWrite *ebml, + guint32 id, + gint64 date) +{ + gst_ebml_write_sint (ebml, id, date); +} + +/* + * Master writing is annoying. We use a size marker of + * the max. allowed length, so that we can later fill it + * in validly. + */ + +guint64 +gst_ebml_write_master_start (GstEbmlWrite *ebml, + guint32 id) +{ + guint64 pos = ebml->pos, t; + GstBuffer *buf = gst_ebml_write_element_new (ebml, 0); + + t = GST_BUFFER_SIZE (buf); + gst_ebml_write_element_id (buf, id); + pos += GST_BUFFER_SIZE (buf) - t; + gst_ebml_write_element_size (buf, -1); + gst_ebml_write_element_push (ebml, buf); + + return pos; +} + +void +gst_ebml_write_master_finish (GstEbmlWrite *ebml, + guint64 startpos) +{ + guint64 pos = ebml->pos; + GstBuffer *buf; + + gst_ebml_write_seek (ebml, startpos); + buf = gst_ebml_write_element_new (ebml, 0); + startpos = GUINT64_TO_BE ((1LLU << 56) | (pos - startpos - 8)); + memcpy (GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf), + (guint8 *) &startpos, 8); + GST_BUFFER_SIZE (buf) += 8; + gst_ebml_write_element_push (ebml, buf); + gst_ebml_write_seek (ebml, pos); +} + +void +gst_ebml_write_binary (GstEbmlWrite *ebml, + guint32 id, + guint8 *binary, + guint64 length) +{ + GstBuffer *buf = gst_ebml_write_element_new (ebml, length); + + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, length); + gst_ebml_write_element_data (buf, binary, length); + gst_ebml_write_element_push (ebml, buf); +} + +/* + * For things like video frames and audio samples, + * you want to use this function, as it doesn't have + * the overhead of memcpy() that other functions + * such as write_binary() do have. + */ + +void +gst_ebml_write_buffer_header (GstEbmlWrite *ebml, + guint32 id, + guint64 length) +{ + GstBuffer *buf = gst_ebml_write_element_new (ebml, 0); + + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, length); + gst_ebml_write_element_push (ebml, buf); +} + +void +gst_ebml_write_buffer (GstEbmlWrite *ebml, + GstBuffer *data) +{ + gst_ebml_write_element_push (ebml, data); +} + +/* + * When replacing a uint, we assume that it is *always* + * 8-byte, since that's the safest guess we can do. This + * is just for simplicity. + * + * FIXME: this function needs to be replaced with something + * proper. This is a crude hack. + */ + +void +gst_ebml_replace_uint (GstEbmlWrite *ebml, + guint64 pos, + guint64 num) +{ + guint64 oldpos = ebml->pos; + GstBuffer *buf = gst_buffer_new_and_alloc (8); + + gst_ebml_write_seek (ebml, pos); + GST_BUFFER_SIZE (buf) = 0; + gst_ebml_write_set_uint (buf, num, 8); + gst_ebml_write_element_push (ebml, buf); + gst_ebml_write_seek (ebml, oldpos); +} + +/* + * Write EBML header. + */ + +void +gst_ebml_write_header (GstEbmlWrite *ebml, + gchar *doctype, + guint version) +{ + guint64 pos; + + /* write the basic EBML header */ + gst_ebml_write_set_cache (ebml, 0x40); + pos = gst_ebml_write_master_start (ebml, GST_EBML_ID_HEADER); +#if (GST_EBML_VERSION != 1) + gst_ebml_write_uint (ebml, GST_EBML_ID_EBMLVERSION, GST_EBML_VERSION); + gst_ebml_write_uint (ebml, GST_EBML_ID_EBMLREADVERSION, GST_EBML_VERSION); +#endif +#if 0 + /* we don't write these until they're "non-default" (never!) */ + gst_ebml_write_uint (ebml, GST_EBML_ID_EBMLMAXIDLENGTH, sizeof (guint32)); + gst_ebml_write_uint (ebml, GST_EBML_ID_EBMLMAXSIZELENGTH, sizeof (guint64)); +#endif + gst_ebml_write_ascii (ebml, GST_EBML_ID_DOCTYPE, doctype); + gst_ebml_write_uint (ebml, GST_EBML_ID_DOCTYPEVERSION, version); + gst_ebml_write_uint (ebml, GST_EBML_ID_DOCTYPEREADVERSION, version); + gst_ebml_write_master_finish (ebml, pos); + gst_ebml_write_flush_cache (ebml); +} diff --git a/gst/matroska/ebml-write.h b/gst/matroska/ebml-write.h new file mode 100644 index 0000000..43d2f8b --- /dev/null +++ b/gst/matroska/ebml-write.h @@ -0,0 +1,126 @@ +/* GStreamer EBML I/O + * (c) 2003 Ronald Bultje + * + * ebml-write.c: write EBML data to file/stream + * + * 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_EBML_WRITE_H__ +#define __GST_EBML_WRITE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_EBML_WRITE \ + (gst_ebml_write_get_type ()) +#define GST_EBML_WRITE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_EBML_WRITE, GstEbmlWrite)) +#define GST_EBML_WRITE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_EBML_WRITE, GstEbmlWriteClass)) +#define GST_IS_EBML_WRITE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_EBML_WRITE)) +#define GST_IS_EBML_WRITE_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_EBML_WRITE)) +#define GST_EBML_WRITE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_EBML_WRITE, GstEbmlWriteClass)) + +typedef struct _GstEbmlWrite { + GstElement parent; + + GstPad *srcpad; + guint64 pos; + + GstBuffer *cache; + guint handled; +} GstEbmlWrite; + +typedef struct _GstEbmlWriteClass { + GstElementClass parent; +} GstEbmlWriteClass; + +GType gst_ebml_write_get_type (void); + +/* + * Caching means that we do not push one buffer for + * each element, but fill this one until a flush. + */ +void gst_ebml_write_set_cache (GstEbmlWrite *ebml, + guint size); +void gst_ebml_write_flush_cache (GstEbmlWrite *ebml); + +/* + * Seeking. + */ +void gst_ebml_write_seek (GstEbmlWrite *ebml, + guint64 pos); + +/* + * Data writing. + */ +void gst_ebml_write_uint (GstEbmlWrite *ebml, + guint32 id, + guint64 num); +void gst_ebml_write_sint (GstEbmlWrite *ebml, + guint32 id, + gint64 num); +void gst_ebml_write_float (GstEbmlWrite *ebml, + guint32 id, + gdouble num); +void gst_ebml_write_ascii (GstEbmlWrite *ebml, + guint32 id, + const gchar *str); +void gst_ebml_write_utf8 (GstEbmlWrite *ebml, + guint32 id, + const gchar *str); +void gst_ebml_write_date (GstEbmlWrite *ebml, + guint32 id, + gint64 date); +guint64 gst_ebml_write_master_start (GstEbmlWrite *ebml, + guint32 id); +void gst_ebml_write_master_finish (GstEbmlWrite *ebml, + guint64 startpos); +void gst_ebml_write_binary (GstEbmlWrite *ebml, + guint32 id, + guchar *binary, + guint64 length); +void gst_ebml_write_header (GstEbmlWrite *ebml, + gchar *doctype, + guint version); + +/* + * Note: this is supposed to be used only for media data. + */ +void gst_ebml_write_buffer_header (GstEbmlWrite *ebml, + guint32 id, + guint64 length); +void gst_ebml_write_buffer (GstEbmlWrite *ebml, + GstBuffer *data); + +/* + * A hack, basically... See matroska-mux.c. I should actually + * make a nice _replace_element_with_size() or so, but this + * works for now. + */ +void gst_ebml_replace_uint (GstEbmlWrite *ebml, + guint64 pos, + guint64 num); + +G_END_DECLS + +#endif /* __GST_EBML_WRITE_H__ */ diff --git a/gst/matroska/matroska-ids.h b/gst/matroska/matroska-ids.h index 455925e..45c13ba 100644 --- a/gst/matroska/matroska-ids.h +++ b/gst/matroska/matroska-ids.h @@ -25,7 +25,7 @@ #include "ebml-ids.h" /* - * EBML element IDs. max. 32-bit. + * Matroska element IDs. max. 32-bit. */ /* toplevel segment */ @@ -141,7 +141,7 @@ #define GST_MATROSKA_CODEC_ID_AUDIO_ACM "A_MS/ACM" #define GST_MATROSKA_CODEC_ID_AUDIO_MPEG2 "A_AAC/MPEG2/" #define GST_MATROSKA_CODEC_ID_AUDIO_MPEG4 "A_AAC/MPEG4/" -/* TODO: AC3-9/10, Real, Musepack, Quicktime */ +/* TODO: AC3-9/10 (?), Real, Musepack, Quicktime */ /* * Enumerations for various types (mapping from binary diff --git a/gst/matroska/matroska-mux.c b/gst/matroska/matroska-mux.c new file mode 100644 index 0000000..2f268d8 --- /dev/null +++ b/gst/matroska/matroska-mux.c @@ -0,0 +1,961 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * + * matroska-mux.c: matroska file/stream muxer + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "matroska-mux.h" +#include "matroska-ids.h" + +enum { + /* FILL ME */ + LAST_SIGNAL +}; + +enum { + ARG_0, + ARG_METADATA, + /* FILL ME */ +}; + +GST_PAD_TEMPLATE_FACTORY (src_templ, + "src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_CAPS_NEW ( + "matroskamux_src", + "video/x-matroska", + NULL + ) +) + +/* FIXME: caps */ + +GST_PAD_TEMPLATE_FACTORY (videosink_templ, + "video_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + NULL +) + +GST_PAD_TEMPLATE_FACTORY (audiosink_templ, + "audio_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + NULL +) + +GST_PAD_TEMPLATE_FACTORY (subtitlesink_templ, + "subtitle_%d", + GST_PAD_SINK, + GST_PAD_REQUEST, + NULL +) + +/* gobject magic foo */ +static void gst_matroska_mux_base_init (GstMatroskaMuxClass *klass); +static void gst_matroska_mux_class_init (GstMatroskaMuxClass *klass); +static void gst_matroska_mux_init (GstMatroskaMux *mux); + +/* element functions */ +static void gst_matroska_mux_loop (GstElement *element); + +/* pad functions */ +static GstPad * gst_matroska_mux_request_new_pad (GstElement *element, + GstPadTemplate *templ, + const gchar *name); + +/* gst internal change state handler */ +static GstElementStateReturn + gst_matroska_mux_change_state (GstElement *element); + +/* gobject bla bla */ +static void gst_matroska_mux_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gst_matroska_mux_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +/* reset muxer */ +static void gst_matroska_mux_reset (GstElement *element); + +static GstEbmlWriteClass *parent_class = NULL; +/*static guint gst_matroska_mux_signals[LAST_SIGNAL] = { 0 };*/ + +GType +gst_matroska_mux_get_type (void) +{ + static GType gst_matroska_mux_type = 0; + + if (!gst_matroska_mux_type) { + static const GTypeInfo gst_matroska_mux_info = { + sizeof (GstMatroskaMuxClass), + (GBaseInitFunc) gst_matroska_mux_base_init, + NULL, + (GClassInitFunc) gst_matroska_mux_class_init, + NULL, + NULL, + sizeof (GstMatroskaMux), + 0, + (GInstanceInitFunc) gst_matroska_mux_init, + }; + + gst_matroska_mux_type = + g_type_register_static (GST_TYPE_EBML_WRITE, + "GstMatroskaMmux", + &gst_matroska_mux_info, 0); + } + + return gst_matroska_mux_type; +} + +static void +gst_matroska_mux_base_init (GstMatroskaMuxClass *klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + static GstElementDetails gst_matroska_mux_details = { + "Matroska muxer", + "Codec/Muxer", + "Muxes video/audio/subtitle streams into a matroska stream", + "Ronald Bultje " + }; + + gst_element_class_add_pad_template (element_class, + GST_PAD_TEMPLATE_GET (videosink_templ)); + gst_element_class_add_pad_template (element_class, + GST_PAD_TEMPLATE_GET (audiosink_templ)); + gst_element_class_add_pad_template (element_class, + GST_PAD_TEMPLATE_GET (subtitlesink_templ)); + gst_element_class_add_pad_template (element_class, + GST_PAD_TEMPLATE_GET (src_templ)); + gst_element_class_set_details (element_class, + &gst_matroska_mux_details); +} + +static void +gst_matroska_mux_class_init (GstMatroskaMuxClass *klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + g_object_class_install_property (gobject_class, ARG_METADATA, + g_param_spec_boxed ("metadata", "Metadata", "Metadata", + GST_TYPE_CAPS, G_PARAM_READWRITE)); + + parent_class = g_type_class_ref (GST_TYPE_EBML_WRITE); + + gobject_class->get_property = gst_matroska_mux_get_property; + gobject_class->set_property = gst_matroska_mux_set_property; + + gstelement_class->change_state = gst_matroska_mux_change_state; + gstelement_class->request_new_pad = gst_matroska_mux_request_new_pad; +} + +static void +gst_matroska_mux_init (GstMatroskaMux *mux) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS (mux); + gint i; + + mux->srcpad = gst_pad_new_from_template ( + gst_element_class_get_pad_template (klass, "src"), "src"); + gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad); + GST_EBML_WRITE (mux)->srcpad = mux->srcpad; + + gst_element_set_loop_function (GST_ELEMENT (mux), + gst_matroska_mux_loop); + + /* initial stream no. */ + for (i = 0; i < GST_MATROSKA_MUX_MAX_STREAMS; i++) { + mux->sink[i].buffer = NULL; + mux->sink[i].track = NULL; + } + mux->index = NULL; + + /* finish off */ + gst_matroska_mux_reset (GST_ELEMENT (mux)); +} + +static void +gst_matroska_mux_reset (GstElement *element) +{ + GstMatroskaMux *mux = GST_MATROSKA_MUX (element); + guint i; + + /* reset input */ + mux->state = GST_MATROSKA_MUX_STATE_START; + + /* clean up existing streams */ + for (i = 0; i < GST_MATROSKA_MUX_MAX_STREAMS; i++) { + if (mux->sink[i].track != NULL) { + if (mux->sink[i].track->pad != NULL) { + gst_element_remove_pad (GST_ELEMENT (mux), mux->sink[i].track->pad); + } + g_free (mux->sink[i].track->codec_id); + g_free (mux->sink[i].track->codec_name); + g_free (mux->sink[i].track->name); + g_free (mux->sink[i].track->language); + g_free (mux->sink[i].track->codec_priv); + g_free (mux->sink[i].track); + mux->sink[i].track = NULL; + } + if (mux->sink[i].buffer != NULL) { + gst_buffer_unref (mux->sink[i].buffer); + mux->sink[i].buffer = NULL; + } + mux->sink[i].eos = FALSE; + } + mux->num_streams = 0; + mux->num_a_streams = 0; + mux->num_t_streams = 0; + mux->num_v_streams = 0; + + /* reset media info (to default) */ + gst_caps_replace (&mux->metadata, + GST_CAPS_NEW ("matroska_metadata", + "application/x-gst-metadata", + "application", GST_PROPS_STRING (""), + "date", GST_PROPS_STRING (""))); + + /* reset indexes */ + mux->num_indexes = 0; + g_free (mux->index); + mux->index = NULL; + + /* reset timers */ + mux->time_scale = 1000000; + mux->duration = 0; +} + +static GstPadLinkReturn +gst_matroska_mux_video_pad_link (GstPad *pad, + GstCaps *caps) +{ + GstMatroskaTrackContext *context = NULL; + GstMatroskaTrackVideoContext *videocontext; + GstMatroskaMux *mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad)); + const gchar *mimetype; + gint width, height, pixel_width, pixel_height, i; + gfloat framerate; + + if (!GST_CAPS_IS_FIXED (caps)) + return GST_PAD_LINK_DELAYED; + + /* find context */ + for (i = 0; i < mux->num_streams; i++) { + if (mux->sink[i].track && mux->sink[i].track->pad && + mux->sink[i].track->pad == pad) { + context = mux->sink[i].track; + break; + } + } + g_assert (i < mux->num_streams); + g_assert (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO); + videocontext = (GstMatroskaTrackVideoContext *) context; + + /* gst -> matroska ID'ing */ + for (; caps != NULL; caps = caps->next) { + mimetype = gst_caps_get_mime (caps); + + /* get general properties */ + gst_caps_get (caps, + "width", &width, + "height", &height, + "framerate", &framerate, + NULL); + videocontext->pixel_width = width; + videocontext->pixel_height = height; + context->default_duration = GST_SECOND / framerate; + + if (gst_caps_has_property (caps, "pixel_width") && + gst_caps_has_property (caps, "pixel_height")) { + gst_caps_get (caps, + "pixel_width", &pixel_width, + "pixel_height", &pixel_height, + NULL); + if (pixel_width > pixel_height) { + videocontext->display_width = width * pixel_width / pixel_height; + videocontext->display_height = height; + } else if (pixel_width < pixel_height) { + videocontext->display_width = width; + videocontext->display_height = height * pixel_height / pixel_width; + } else { + videocontext->display_width = 0; + videocontext->display_height = 0; + } + } else { + videocontext->display_width = 0; + videocontext->display_height = 0; + } + + videocontext->asr_mode = GST_MATROSKA_ASPECT_RATIO_MODE_FREE; + videocontext->eye_mode = GST_MATROSKA_EYE_MODE_MONO; + videocontext->fourcc = 0; + + /* find type */ + if (!strcmp (mimetype, "video/x-raw-yuv")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED); + gst_caps_get_fourcc_int (caps, "format", &videocontext->fourcc); + + return GST_PAD_LINK_OK; + } else if (!strcmp (mimetype, "video/x-jpeg")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG); + + return GST_PAD_LINK_OK; + } else if (!strcmp (mimetype, "video/x-divx")) { + gint divxversion; + + gst_caps_get_int (caps, "divxversion", &divxversion); + switch (divxversion) { + case 3: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3); + break; + case 4: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_SP); + break; + case 5: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP); + break; + } + + return GST_PAD_LINK_OK; + } else if (!strcmp (mimetype, "video/x-xvid")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP); + + return GST_PAD_LINK_OK; + } else if (!strcmp (mimetype, "video/mpeg")) { + gint mpegversion; + + gst_caps_get_int (caps, "mpegversion", &mpegversion); + switch (mpegversion) { + case 1: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG1); + break; + case 2: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG2); + break; + case 3: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP); + break; + } + + return GST_PAD_LINK_OK; + } else if (!strcmp (mimetype, "video/x-msmpeg")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3); + + return GST_PAD_LINK_OK; + } + } + + return GST_PAD_LINK_REFUSED; +} + +static GstPadLinkReturn +gst_matroska_mux_audio_pad_link (GstPad *pad, + GstCaps *caps) +{ + GstMatroskaTrackContext *context = NULL; + GstMatroskaTrackAudioContext *audiocontext; + GstMatroskaMux *mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad)); + const gchar *mimetype; + gint samplerate, channels, i; + + if (!GST_CAPS_IS_FIXED (caps)) + return GST_PAD_LINK_DELAYED; + + /* find context */ + for (i = 0; i < mux->num_streams; i++) { + if (mux->sink[i].track && mux->sink[i].track->pad && + mux->sink[i].track->pad == pad) { + context = mux->sink[i].track; + break; + } + } + g_assert (i < mux->num_streams); + g_assert (context->type == GST_MATROSKA_TRACK_TYPE_AUDIO); + audiocontext = (GstMatroskaTrackAudioContext *) context; + + for (; caps != NULL; caps = caps->next) { + mimetype = gst_caps_get_mime (caps); + + /* general setup */ + gst_caps_get (caps, + "rate", &samplerate, + "channels", &channels, + NULL); + audiocontext->samplerate = samplerate; + audiocontext->channels = channels; + audiocontext->bitdepth = 16; + + if (!strcmp (mimetype, "audio/mpeg")) { + gint mpegversion = 1; + + gst_caps_get_int (caps, "mpegversion", &mpegversion); + switch (mpegversion) { + case 1: { + gint layer; + + gst_caps_get_int (caps, "layer", &layer); + switch (layer) { + case 1: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1); + break; + case 2: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2); + break; + case 3: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3); + break; + } + break; + } + case 2: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG2 + "MAIN"); + break; + case 4: + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG4 + "MAIN"); + break; + } + + return GST_PAD_LINK_OK; + } else if (!strcmp (mimetype, "audio/x-raw-int")) { + gint endianness, width, depth; + gboolean signedness; + + gst_caps_get (caps, + "endianness", &endianness, + "width", &width, + "depth", &depth, + "signed", &signedness, + NULL); + if (width != depth || + (width == 8 && signedness) || (width == 16 && !signedness)) + continue; + + audiocontext->bitdepth = depth; + if (endianness == G_BIG_ENDIAN) + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE); + else + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE); + + return GST_PAD_LINK_OK; + } else if (!strcmp (mimetype, "audio/x-raw-float")) { + /* FIXME: endianness is undefined */ + } else if (!strcmp (mimetype, "audio/x-vorbis")) { + /* FIXME: private data setup needs work */ + } else if (!strcmp (mimetype, "audio/x-ac3")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3); + + return GST_PAD_LINK_OK; + } + } + + return GST_PAD_LINK_REFUSED; +} + +static GstPadLinkReturn +gst_matroska_mux_subtitle_pad_link (GstPad *pad, + GstCaps *caps) +{ + /* Consider this as boilerplate code for now. There is + * no single subtitle creation element in GStreamer, + * neither do I know how subtitling works at all. */ + + return GST_PAD_LINK_REFUSED; +} + +static GstPad * +gst_matroska_mux_request_new_pad (GstElement *element, + GstPadTemplate *templ, + const gchar *pad_name) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); + GstMatroskaMux *mux = GST_MATROSKA_MUX (element); + GstPad *pad = NULL; + gchar *name = NULL; + GstPadLinkFunction linkfunc = NULL; + GstMatroskaTrackContext *context = NULL; + + if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) { + name = g_strdup_printf ("audio_%d", mux->num_a_streams++); + linkfunc = gst_matroska_mux_audio_pad_link; + context = (GstMatroskaTrackContext *) + g_new0 (GstMatroskaTrackAudioContext, 1); + context->type = GST_MATROSKA_TRACK_TYPE_AUDIO; + context->name = g_strdup ("Audio"); + } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) { + name = g_strdup_printf ("video_%d", mux->num_v_streams++); + linkfunc = gst_matroska_mux_video_pad_link; + context = (GstMatroskaTrackContext *) + g_new0 (GstMatroskaTrackVideoContext, 1); + context->type = GST_MATROSKA_TRACK_TYPE_VIDEO; + context->name = g_strdup ("Video"); + } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%d")) { + name = g_strdup_printf ("subtitle_%d", mux->num_t_streams++); + linkfunc = gst_matroska_mux_subtitle_pad_link; + context = (GstMatroskaTrackContext *) + g_new0 (GstMatroskaTrackSubtitleContext, 1); + context->type = GST_MATROSKA_TRACK_TYPE_SUBTITLE; + context->name = g_strdup ("Subtitle"); + } else { + g_warning ("matroskamux: this is not our template!"); + return NULL; + } + + pad = gst_pad_new_from_template (templ, name); + g_free (name); + gst_element_add_pad (element, pad); + gst_pad_set_link_function (pad, linkfunc); + context->index = mux->num_streams++; + mux->sink[context->index].track = context; + context->pad = pad; + context->flags = GST_MATROSKA_TRACK_ENABLED | + GST_MATROSKA_TRACK_DEFAULT; + + return pad; +} + +static void +gst_matroska_mux_track_header (GstMatroskaMux *mux, + GstMatroskaTrackContext *context) +{ + GstEbmlWrite *ebml = GST_EBML_WRITE (mux); + guint64 master; + + /* track type goes before the type-specific stuff */ + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKNUMBER, context->num); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKTYPE, context->type); + + /* type-specific stuff */ + switch (context->type) { + case GST_MATROSKA_TRACK_TYPE_VIDEO: { + GstMatroskaTrackVideoContext *videocontext = + (GstMatroskaTrackVideoContext *) context; + + /* framerate, but not in the video part */ + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKDEFAULTDURATION, + context->default_duration); + + master = gst_ebml_write_master_start (ebml, + GST_MATROSKA_ID_TRACKVIDEO); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELWIDTH, + videocontext->pixel_width); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELHEIGHT, + videocontext->pixel_height); + if (videocontext->display_width && videocontext->display_height) { + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYWIDTH, + videocontext->display_width); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYHEIGHT, + videocontext->display_height); + } + if (context->flags & GST_MATROSKA_VIDEOTRACK_INTERLACED) + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOFLAGINTERLACED, 1); + if (videocontext->fourcc) { + guint32 fcc_le = GUINT32_TO_LE (videocontext->fourcc); + gst_ebml_write_binary (ebml, GST_MATROSKA_ID_VIDEOCOLOURSPACE, + (gpointer) &fcc_le, 4); + } + gst_ebml_write_master_finish (ebml, master); + + break; + } + + case GST_MATROSKA_TRACK_TYPE_AUDIO: { + GstMatroskaTrackAudioContext *audiocontext = + (GstMatroskaTrackAudioContext *) context; + + master = gst_ebml_write_master_start (ebml, + GST_MATROSKA_ID_TRACKAUDIO); + if (audiocontext->samplerate != 8000) + gst_ebml_write_float (ebml, GST_MATROSKA_ID_AUDIOSAMPLINGFREQ, + audiocontext->samplerate); + if (audiocontext->channels != 1) + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOCHANNELS, + audiocontext->channels); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOBITDEPTH, + audiocontext->bitdepth); + gst_ebml_write_master_finish (ebml, master); + + break; + } + + default: + /* doesn't need type-specific data */ + break; + } + + gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CODECID, + context->codec_id); + if (context->codec_priv) + gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECPRIVATE, + context->codec_priv, context->codec_priv_size); + /* FIXME: until we have a nice way of getting the codecname + * out of the caps, I'm not going to enable this. Too much + * (useless, double, boring) work... */ + /*gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CODECNAME, + context->codec_name);*/ + gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKNAME, + context->name); +} + +static void +gst_matroska_mux_start (GstMatroskaMux *mux) +{ + GstEbmlWrite *ebml = GST_EBML_WRITE (mux); + guint32 seekhead_id[] = { GST_MATROSKA_ID_INFO, + GST_MATROSKA_ID_TRACKS, + GST_MATROSKA_ID_CUES, +#if 0 + GST_MATROSKA_ID_TAGS, +#endif + 0 }; + guint64 master, child; + gint i; + guint tracknum = 1; + + /* we start with a EBML header */ + gst_ebml_write_header (ebml, "matroska", 1); + + /* start a segment */ + mux->segment_pos = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENT); + mux->segment_master = ebml->pos; + + /* the rest of the header is cached */ + gst_ebml_write_set_cache (ebml, 0x1000); + + /* seekhead (table of contents) - we set the positions later */ + mux->seekhead_pos = ebml->pos; + master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD); + for (i = 0; seekhead_id[i] != 0; i++) { + child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKENTRY); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, seekhead_id[i]); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION, -1); + gst_ebml_write_master_finish (ebml, child); + } + gst_ebml_write_master_finish (ebml, master); + + /* segment info */ + mux->info_pos = ebml->pos; + master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_INFO); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TIMECODESCALE, mux->time_scale); + mux->duration_pos = ebml->pos; + gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION, 0); + gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_MUXINGAPP, "GStreamer"); + if (mux->metadata && + gst_caps_has_property (mux->metadata, "application")) { + const gchar *app; + + gst_caps_get_string (mux->metadata, "application", &app); + if (app && app[0]) { + gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_WRITINGAPP, app); + } + } + /* FIXME: how do I get this? Automatic? Via tags? */ + /*gst_ebml_write_date (ebml, GST_MATROSKA_ID_DATEUTC, 0);*/ + gst_ebml_write_master_finish (ebml, master); + + /* tracks */ + mux->tracks_pos = ebml->pos; + master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKS); + for (i = 0; i < mux->num_streams; i++) { + if (GST_PAD_IS_USABLE (mux->sink[i].track->pad)) { + mux->sink[i].track->num = tracknum++; + child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKENTRY); + gst_matroska_mux_track_header (mux, mux->sink[i].track); + gst_ebml_write_master_finish (ebml, child); + } + } + gst_ebml_write_master_finish (ebml, master); + + /* lastly, flush the cache */ + gst_ebml_write_flush_cache (ebml); +} + +static void +gst_matroska_mux_finish (GstMatroskaMux *mux) +{ + GstEbmlWrite *ebml = GST_EBML_WRITE (mux); + guint64 pos; + + /* cues */ + if (mux->index != NULL) { + guint n; + guint64 master, pointentry_master, trackpos_master; + + mux->cues_pos = ebml->pos; + gst_ebml_write_set_cache (ebml, 12 + 41 * mux->num_indexes); + master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CUES); + + for (n = 0; n < mux->num_indexes; n++) { + GstMatroskaIndex *idx = &mux->index[n]; + + pointentry_master = gst_ebml_write_master_start (ebml, + GST_MATROSKA_ID_POINTENTRY); + gst_ebml_write_date (ebml, GST_MATROSKA_ID_CUETIME, + idx->time / mux->time_scale); + trackpos_master = gst_ebml_write_master_start (ebml, + GST_MATROSKA_ID_CUETRACKPOSITION); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETRACK, idx->track); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUECLUSTERPOSITION, + idx->pos - mux->segment_master); + gst_ebml_write_master_finish (ebml, trackpos_master); + gst_ebml_write_master_finish (ebml, pointentry_master); + } + + gst_ebml_write_master_finish (ebml, master); + gst_ebml_write_flush_cache (ebml); + } + + /* FIXME: tags */ + + /* update seekhead. We know that: + * - a seekhead contains 4 entries. + * - order of entries is as above. + * - a seekhead has a 4-byte header + 8-byte length + * - each entry is 2-byte master, 2-byte ID pointer, + * 2-byte length pointer, all 8/1-byte length, 4- + * byte ID and 8-byte length pointer, where the + * length pointer starts at 20. + * - all entries are local to the segment (so pos - segment_master). + * - so each entry is at 12 + 20 + num * 28. */ + gst_ebml_replace_uint (ebml, mux->seekhead_pos + 32, + mux->info_pos - mux->segment_master); + gst_ebml_replace_uint (ebml, mux->seekhead_pos + 60, + mux->tracks_pos - mux->segment_master); + if (mux->index != NULL) { + gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88, + mux->cues_pos - mux->segment_master); + } else { + /* void'ify */ + guint64 my_pos = ebml->pos; + gst_ebml_write_seek (ebml, mux->seekhead_pos + 68); + gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26); + gst_ebml_write_seek (ebml, my_pos); + } +#if 0 + gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116, + mux->tags_pos - mux->segment_master); +#endif + + /* update duration */ + pos = GST_EBML_WRITE (mux)->pos; + gst_ebml_write_seek (ebml, mux->duration_pos); + gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION, + mux->duration / mux->time_scale); + gst_ebml_write_seek (ebml, pos); + + /* finish segment - this also writes element length */ + gst_ebml_write_master_finish (ebml, mux->segment_pos); +} + +static gint +gst_matroska_mux_prepare_data (GstMatroskaMux *mux) +{ + gint i, first = -1; + + for (i = 0; i < mux->num_streams; i++) { + while (!mux->sink[i].eos && !mux->sink[i].buffer && + mux->sink[i].track->num > 0 && + GST_PAD_IS_USABLE (mux->sink[i].track->pad)) { + GstData *data; + + data = gst_pad_pull (mux->sink[i].track->pad); + if (GST_IS_EVENT (data)) { + if (GST_EVENT_TYPE (GST_EVENT (data)) == GST_EVENT_EOS) + mux->sink[i].eos = TRUE; + gst_event_unref (GST_EVENT (data)); + } else { + mux->sink[i].buffer = GST_BUFFER (data); + } + } + + if (mux->sink[i].buffer) { + if (first < 0 || GST_BUFFER_TIMESTAMP (mux->sink[i].buffer) < + GST_BUFFER_TIMESTAMP (mux->sink[first].buffer)) + first = i; + } + } + + return first; +} + +static void +gst_matroska_mux_write_data (GstMatroskaMux *mux) +{ + GstEbmlWrite *ebml = GST_EBML_WRITE (mux); + GstBuffer *buf, *hdr; + gint i; + guint64 cluster, blockgroup; + + /* which stream-num to write from? */ + if ((i = gst_matroska_mux_prepare_data (mux)) < 0) { + GstEvent *event = gst_event_new (GST_EVENT_EOS); + + gst_matroska_mux_finish (mux); + gst_pad_push (mux->srcpad, GST_DATA (event)); + gst_element_set_eos (GST_ELEMENT (mux)); + + return; + } + + /* write data */ + buf = mux->sink[i].buffer; + mux->sink[i].buffer = NULL; + + /* We currently write an index entry for each keyframe in a + * video track. This can be largely improved, such as doing + * one for each keyframe or each second (for all-keyframe + * streams), only the *first* video track or the audio track + * if we have no video tracks. But that'll come later... */ + if (mux->sink[i].track->type == GST_MATROSKA_TRACK_TYPE_VIDEO && + GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_KEY_UNIT)) { + GstMatroskaIndex *idx; + + if (mux->num_indexes % 32 == 0) { + mux->index = g_renew (GstMatroskaIndex, mux->index, + mux->num_indexes + 32); + } + idx = &mux->index[mux->num_indexes++]; + + idx->pos = ebml->pos; + idx->time = GST_BUFFER_TIMESTAMP (buf); + idx->track = mux->sink[i].track->num; + } + + /* write one cluster with one blockgroup with one block with + * one slice (*breath*). + * FIXME: lacing, multiple frames/cluster, etc. */ + cluster = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE, + GST_BUFFER_TIMESTAMP (buf) / mux->time_scale); + blockgroup = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_BLOCKGROUP); + gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_BLOCK, + GST_BUFFER_SIZE (buf) + 4); + hdr = gst_buffer_new_and_alloc (4); + /* track num - FIXME: what if num >= 0x80 (unlikely)? */ + GST_BUFFER_DATA (hdr)[0] = mux->sink[i].track->num | 0x80; + /* time relative to clustertime - we don't use this yet */ + * (guint16 *) &GST_BUFFER_DATA (hdr)[1] = GUINT16_TO_BE (0); + /* flags - no lacing (yet) */ + GST_BUFFER_DATA (hdr)[3] = 0; + gst_ebml_write_buffer (ebml, hdr); + gst_ebml_write_buffer (ebml, buf); + gst_ebml_write_master_finish (ebml, blockgroup); + gst_ebml_write_master_finish (ebml, cluster); +} + +static void +gst_matroska_mux_loop (GstElement *element) +{ + GstMatroskaMux *mux = GST_MATROSKA_MUX (element); + + /* start with a header */ + if (mux->state == GST_MATROSKA_MUX_STATE_START) { + mux->state = GST_MATROSKA_MUX_STATE_HEADER; + gst_matroska_mux_start (mux); + mux->state = GST_MATROSKA_MUX_STATE_DATA; + } + + /* do one single buffer */ + gst_matroska_mux_write_data (mux); +} + +static GstElementStateReturn +gst_matroska_mux_change_state (GstElement *element) +{ + GstMatroskaMux *mux = GST_MATROSKA_MUX (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_PAUSED_TO_READY: + gst_matroska_mux_reset (GST_ELEMENT (mux)); + break; + default: + break; + } + + if (((GstElementClass *) parent_class)->change_state) + return ((GstElementClass *) parent_class)->change_state (element); + + return GST_STATE_SUCCESS; +} + +static void +gst_matroska_mux_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GstMatroskaMux *mux; + + g_return_if_fail (GST_IS_MATROSKA_MUX (object)); + mux = GST_MATROSKA_MUX (object); + + switch (prop_id) { + case ARG_METADATA: + gst_caps_replace (&mux->metadata, + g_value_get_boxed (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_matroska_mux_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GstMatroskaMux *mux; + + g_return_if_fail (GST_IS_MATROSKA_MUX (object)); + mux = GST_MATROSKA_MUX (object); + + switch (prop_id) { + case ARG_METADATA: + g_value_set_boxed (value, mux->metadata); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +gboolean +gst_matroska_mux_plugin_init (GstPlugin *plugin) +{ + return gst_element_register (plugin, "matroskamux", + GST_RANK_NONE, + GST_TYPE_MATROSKA_MUX); +} diff --git a/gst/matroska/matroska-mux.h b/gst/matroska/matroska-mux.h new file mode 100644 index 0000000..b89f0d2 --- /dev/null +++ b/gst/matroska/matroska-mux.h @@ -0,0 +1,103 @@ +/* GStreamer Matroska muxer/demuxer + * (c) 2003 Ronald Bultje + * + * matroska-mux.h: matroska file/stream muxer object types + * + * 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_MATROSKA_MUX_H__ +#define __GST_MATROSKA_MUX_H__ + +#include + +#include "ebml-write.h" +#include "matroska-ids.h" + +G_BEGIN_DECLS + +#define GST_TYPE_MATROSKA_MUX \ + (gst_matroska_mux_get_type ()) +#define GST_MATROSKA_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_MATROSKA_MUX, GstMatroskaMux)) +#define GST_MATROSKA_MUX_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_MATROSKA_MUX, GstMatroskaMux)) +#define GST_IS_MATROSKA_MUX(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_MATROSKA_MUX)) +#define GST_IS_MATROSKA_MUX_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_MATROSKA_MUX)) + +#define GST_MATROSKA_MUX_MAX_STREAMS 64 + +typedef enum { + GST_MATROSKA_MUX_STATE_START, + GST_MATROSKA_MUX_STATE_HEADER, + GST_MATROSKA_MUX_STATE_DATA, +} GstMatroskaMuxState; + +typedef struct _GstMatroskaMux { + GstEbmlWrite parent; + + /* pads */ + GstPad *srcpad; + struct { + GstMatroskaTrackContext *track; + GstBuffer *buffer; + gboolean eos; + } sink[GST_MATROSKA_MUX_MAX_STREAMS]; + guint num_streams, + num_v_streams, num_a_streams, num_t_streams; + + /* metadata - includes writing_app and creation_time */ + GstCaps *metadata; + + /* state */ + GstMatroskaMuxState state; + + /* a cue (index) table */ + GstMatroskaIndex *index; + guint num_indexes; + + /* timescale in the file */ + guint64 time_scale; + + /* length, position (time, ns) */ + guint64 duration; + + /* byte-positions of master-elements (for replacing contents) */ + guint64 segment_pos, + seekhead_pos, + cues_pos, +#if 0 + tags_pos, +#endif + info_pos, + tracks_pos, + duration_pos; + guint64 segment_master; +} GstMatroskaMux; + +typedef struct _GstMatroskaMuxClass { + GstEbmlWriteClass parent; +} GstMatroskaMuxClass; + +GType gst_matroska_mux_get_type (void); + +gboolean gst_matroska_mux_plugin_init (GstPlugin *plugin); + +G_END_DECLS + +#endif /* __GST_MATROSKA_MUX_H__ */ diff --git a/gst/matroska/matroska.c b/gst/matroska/matroska.c index feb44d6..4965781 100644 --- a/gst/matroska/matroska.c +++ b/gst/matroska/matroska.c @@ -24,11 +24,13 @@ #endif #include "matroska-demux.h" +#include "matroska-mux.h" static gboolean plugin_init (GstPlugin *plugin) { - return (gst_matroska_demux_plugin_init (plugin)); + return (gst_matroska_demux_plugin_init (plugin) && + gst_matroska_mux_plugin_init (plugin)); } GST_PLUGIN_DEFINE ( -- 2.7.4