diff --git a/gst/avi/Makefile.am b/gst/avi/Makefile.am index 0b81ccc35c..cbea2b3ad5 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 0000000000..f9e2dd316a --- /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 0000000000..da30d4341e --- /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 ad4531a02d..5f19fce5e7 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_avi_demux_init (GstAviDemux *avi) { - 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_FLAG_SET (avi, GST_ELEMENT_EVENT_AWARE); - gst_element_set_loop_function (GST_ELEMENT (avi_demux), gst_avi_demux_loop); + 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; + + gst_element_set_loop_function (GST_ELEMENT (avi), gst_avi_demux_loop); + gst_avi_demux_reset (avi); + + avi->streaminfo = NULL; + avi->index_entries = NULL; + memset (&avi->stream, 0, sizeof (avi->stream)); } -static gboolean -gst_avi_demux_avih (GstAviDemux *avi_demux) +static void +gst_avi_demux_reset (GstAviDemux *avi) { - gst_riff_avih *avih; - guint8 *avihdata; - GstByteStream *bs = avi_demux->bs; - guint32 got_bytes; + gint i; - 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; + 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); } - return FALSE; -} + memset (&avi->stream, 0, sizeof (avi->stream)); -static gboolean -gst_avi_demux_strh (GstAviDemux *avi_demux) -{ - gst_riff_strh *strh; - guint8 *strhdata; - GstByteStream *bs = avi_demux->bs; - guint32 got_bytes; + avi->num_streams = 0; + avi->num_v_streams = 0; + avi->num_a_streams = 0; - got_bytes = gst_bytestream_peek_bytes (bs, &strhdata, sizeof (gst_riff_strh)); - strh = (gst_riff_strh *) strhdata; + avi->state = GST_AVI_DEMUX_START; + avi->level_up = 0; - 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; + if (avi->index_entries) { + g_free (avi->index_entries); + avi->index_entries = NULL; } - return FALSE; + avi->index_size = 0; + + avi->num_frames = 0; + avi->us_per_frame = 0; + + avi->seek_offset = (guint64) -1; + + gst_caps_replace (&avi->streaminfo, NULL); } static void -gst_avi_demux_dmlh (GstAviDemux *avi_demux) -{ - gst_riff_dmlh *dmlh; - guint8 *dmlhdata; - GstByteStream *bs = avi_demux->bs; - guint32 got_bytes; - - 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; - - got_bytes = gst_bytestream_peek_bytes (bs, &namedata, len); - name = (gchar *) namedata; - if (got_bytes != len) - return; - - GST_DEBUG ("Stream name: \"%s\"", name); -} - -static void -gst_avi_demux_metadata (GstAviDemux *avi_demux, gint len) -{ - 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); - - gst_bytestream_flush (bs, sizeof (gst_riff_chunk)); - if (got_bytes != sizeof (gst_riff_chunk)) - return; - len -= sizeof (gst_riff_chunk); - - /* don't care about empty entries - move on */ - if (chunk.size == 0) - continue; - - 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; - } - - if (type) { - /* create props entry */ - entry = gst_props_entry_new (type, GST_PROPS_STRING (name)); - gst_props_add_entry (props, entry); - } - } - - gst_props_debug(props); - - gst_caps_replace_sink (&avi_demux->metadata, - gst_caps_new("avi_metadata", - "application/x-gst-metadata", - props)); - - g_object_notify(G_OBJECT(avi_demux), "metadata"); -} - -static void -gst_avi_demux_streaminfo (GstAviDemux *avi_demux) +gst_avi_demux_streaminfo (GstAviDemux *avi) { GstProps *props; @@ -516,748 +238,25 @@ 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) -{ - 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; - - 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; - - 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)); - 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; - - 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; - - 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; - - 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; - - 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; - - 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; - - 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; - - 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; - - default: - g_warning ("avidemux: unkown video format " GST_FOURCC_FORMAT, - GST_FOURCC_ARGS(codec_fcc)); - break; - } - - /* 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); - } - - return caps; -} - -static void -gst_avi_demux_strf_vids (GstAviDemux *avi_demux) -{ - 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; - - 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; - - padname = g_strdup_printf ("video_%02d", avi_demux->num_v_streams); - srcpad = gst_pad_new_from_template (videosrctempl, padname); - g_free (padname); - - /* 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 (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); -} - -#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) -{ - 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); - } - - 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; - - 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; - - case GST_RIFF_WAVE_FORMAT_PCM: /* PCM/wav */ { - GstPropsEntry *width = NULL, *depth = NULL, *signedness = NULL; - - if (strf != NULL) { - gint ba = GUINT16_FROM_LE (strf->blockalign); - gint ch = GUINT16_FROM_LE (strf->channels); - gint ws = GUINT16_FROM_LE (strf->size); - - 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))); - } - - 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); - } - 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); - } - 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); - } - 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)); -} - -static void -gst_avi_demux_strf_iavs (GstAviDemux *avi_demux) -{ - 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 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); -} - -static void -gst_avi_demux_parse_index (GstAviDemux *avi_demux, - gulong filepos, gulong offset) -{ - 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) +static gst_avi_index_entry * +gst_avi_demux_index_next (GstAviDemux *avi, + 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]; + for (i = start; i < avi->index_size; i++) { + entry = &avi->index_entries[i]; if (entry->stream_nr == stream_nr && (entry->flags & flags) == flags) { break; @@ -1267,15 +266,18 @@ gst_avi_demux_index_next (GstAviDemux *avi_demux, gint stream_nr, gint start, gu return entry; } -static gst_avi_index_entry* -gst_avi_demux_index_entry_for_time (GstAviDemux *avi_demux, gint stream_nr, guint64 time, guint32 flags) +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; i = -1; do { - entry = gst_avi_demux_index_next (avi_demux, stream_nr, i + 1, flags); + entry = gst_avi_demux_index_next (avi, stream_nr, i + 1, flags); if (!entry) return NULL; @@ -1284,13 +286,62 @@ gst_avi_demux_index_entry_for_time (GstAviDemux *avi_demux, gint stream_nr, guin if (entry->ts <= time) { last_entry = entry; } - } - while (entry->ts <= time); + } while (entry->ts <= time); return last_entry; } -static const GstFormat* +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; + + i = -1; + do { + entry = gst_avi_demux_index_next (avi, stream_nr, i + 1, flags); + if (!entry) + return NULL; + + i = entry->index_nr; + + if (entry->bytes_before <= byte) { + last_entry = entry; + } + } while (entry->bytes_before <= byte); + + return last_entry; +} + +static gst_avi_index_entry * +gst_avi_demux_index_entry_for_frame (GstAviDemux *avi, + gint stream_nr, + guint32 frame, + guint32 flags) +{ + gst_avi_index_entry *entry = NULL, *last_entry = NULL; + gint i; + + i = -1; + do { + entry = gst_avi_demux_index_next (avi, stream_nr, i + 1, flags); + if (!entry) + return NULL; + + i = entry->index_nr; + + if (entry->frames_before <= frame) { + last_entry = entry; + } + } while (entry->frames_before <= frame); + + return last_entry; +} + +static const GstFormat * gst_avi_demux_get_src_formats (GstPad *pad) { avi_stream_context *stream = gst_pad_get_element_private (pad); @@ -1307,28 +358,36 @@ gst_avi_demux_get_src_formats (GstPad *pad) 0 }; - return (stream->strh.type == GST_RIFF_FCC_auds ? src_a_formats : src_v_formats); + 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) +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 (stream->strh.type != GST_RIFF_FCC_auds && - (src_format == GST_FORMAT_BYTES || *dest_format == GST_FORMAT_BYTES)) + 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); + *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); + *dest_value = src_value * stream->strh->rate / + (stream->strh->scale * GST_SECOND); break; default: res = FALSE; @@ -1338,7 +397,7 @@ gst_avi_demux_src_convert (GstPad *pad, GstFormat src_format, gint64 src_value, case GST_FORMAT_BYTES: switch (*dest_format) { case GST_FORMAT_TIME: - *dest_value = ((gfloat)src_value) * GST_SECOND / stream->strh.rate; + *dest_value = ((gfloat) src_value) * GST_SECOND / stream->strh->rate; break; default: res = FALSE; @@ -1348,7 +407,8 @@ gst_avi_demux_src_convert (GstPad *pad, GstFormat src_format, gint64 src_value, case GST_FORMAT_DEFAULT: switch (*dest_format) { case GST_FORMAT_TIME: - *dest_value = ((((gfloat)src_value) * stream->strh.scale) / stream->strh.rate) * GST_SECOND; + *dest_value = ((((gfloat) src_value) * stream->strh->scale) / + stream->strh->rate) * GST_SECOND; break; default: res = FALSE; @@ -1362,7 +422,7 @@ gst_avi_demux_src_convert (GstPad *pad, GstFormat src_format, gint64 src_value, return res; } -static const GstQueryType* +static const GstQueryType * gst_avi_demux_get_src_query_types (GstPad *pad) { static const GstQueryType src_types[] = { @@ -1375,31 +435,34 @@ gst_avi_demux_get_src_query_types (GstPad *pad) } static gboolean -gst_avi_demux_handle_src_query (GstPad *pad, GstQueryType type, - GstFormat *format, gint64 *value) +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)); + /*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); - - 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"); - break; - case GST_EVENT_DISCONTINUOUS: - { - gint i; - GstEvent *discont; - - for (i = 0; i < avi_demux->num_streams; i++) { - avi_stream_context *stream = &avi_demux->stream[i]; - - 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); - - discont = gst_event_new_discontinuous (FALSE, GST_FORMAT_TIME, - avi_demux->last_seek + stream->delay , NULL); - - gst_pad_push (stream->pad, GST_DATA (discont)); - } - } - break; - } - default: - g_warning ("unhandled event %d", type); - break; - } - - gst_event_unref (event); - -done: - - return res; -} - - -static void -gst_avi_demux_loop (GstElement *element) -{ - 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"); - } - 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; - } - - chunk.id = GUINT32_FROM_LE (temp_chunk->id); - chunk.size = GUINT32_FROM_LE (temp_chunk->size); - - switch (chunk.id) { - case GST_RIFF_TAG_RIFF: - 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; - } - chunk.type = GUINT32_FROM_LE (temp_chunk->type); - skipsize = sizeof (gst_riff_list); - break; - default: - skipsize = sizeof (gst_riff_chunk); - 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; - - 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; - } - 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; - } - break; - } - - while (flush) { - gboolean res; - - res = gst_bytestream_flush (avi_demux->bs, flush); - if (!res) { - guint32 remaining; - GstEvent *event; - - gst_bytestream_get_status (avi_demux->bs, &remaining, &event); - gst_event_unref (event); - } - else - break; - } -} - -static GstElementStateReturn -gst_avi_demux_change_state (GstElement *element) -{ - GstAviDemux *avi_demux = 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: - 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: - break; - default: - break; - } - - parent_class->change_state (element); - - return GST_STATE_SUCCESS; -} - -static void -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); - - 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); - break; - default: - break; - } -} - -static gboolean -plugin_init (GstPlugin *plugin) -{ - if (!gst_library_load ("gstbytestream")) + if (!gst_riff_read_header (riff, &doctype)) 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)) { + if (doctype != GST_RIFF_RIFF_AVI) { + gst_element_error (GST_ELEMENT (avi), "Not an AVI file"); return FALSE; } return TRUE; } +/* + * Read 'avih' header. + */ -GST_PLUGIN_DEFINE ( - GST_VERSION_MAJOR, - GST_VERSION_MINOR, - "avimux", - "AVI stream handling", - plugin_init, - VERSION, - GST_LICENSE, - GST_PACKAGE, - GST_ORIGIN -) +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; + default: + g_warning ("Unknown stream type " GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (strh->type)); + goto skip_stream; + } + + /* 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; + } + + switch (tag) { + case GST_RIFF_TAG_strn: + if (name) + g_free (name); + if (!gst_riff_read_ascii (riff, &tag, &name)) + return FALSE; + break; + + 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; + } + + 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_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); + + return TRUE; +} + +/* + * 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. + */ + +gboolean +gst_avi_demux_stream_header (GstAviDemux *avi) +{ + 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; + } + } + + 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. */ + + 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)); + } + } + + 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: + 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; + } + + break; + + default: + 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; + } + } + + /* 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; + + /* 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); + } + } + 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)); + } + } + } + + return TRUE; +} + +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 = GST_AVI_DEMUX (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_READY_TO_PAUSED: + gst_avi_demux_streaminfo (avi); + break; + case GST_STATE_PAUSED_TO_READY: + gst_avi_demux_reset (avi); + break; + default: + break; + } + + 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, + GParamSpec *pspec) +{ + GstAviDemux *avi = GST_AVI_DEMUX (object); + + switch (prop_id) { + case ARG_STREAMINFO: + g_value_set_boxed (value, avi->streaminfo); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} diff --git a/gst/avi/gstavidemux.h b/gst/avi/gstavidemux.h index a9334283df..15eec1f375 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; - guint64 total_bytes; - guint32 total_frames; + /* pad, strh */ + GstPad *pad; + gst_riff_strh *strh; - guint32 skip; + /* current position (byte, frame, time) */ + guint current_frame; + guint64 current_byte; + gint current_entry; + /* 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; + +typedef struct _GstAviDemuxClass { + GstRiffReadClass parent_class; +} GstAviDemuxClass; GType gst_avi_demux_get_type (void); -#ifdef __cplusplus -} -#endif /* __cplusplus */ - +G_END_DECLS #endif /* __GST_AVI_DEMUX_H__ */ diff --git a/gst/avi/gstavimux.h b/gst/avi/gstavimux.h index 31c64c3838..452b72212d 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 b0235d8998..7efb21af2b 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 a6b9225d91..67b9b5f34d 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 52e4e141b6..d78d13ce72 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 0000000000..9c76895372 --- /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 0000000000..43d2f8b259 --- /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 455925efca..45c13baa33 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 0000000000..2f268d8aa7 --- /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 0000000000..b89f0d2215 --- /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 feb44d632f..4965781858 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 (