diff -r 567bb019e3e3 -r 7e817e7e631c gst_plugins_base/ext/ogg/gstogmparse.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gst_plugins_base/ext/ogg/gstogmparse.c Wed Sep 01 12:16:41 2010 +0100 @@ -0,0 +1,956 @@ +/* GStreamer OGM parsing + * Copyright (C) 2004 Ronald Bultje + * Copyright (C) 2006 Tim-Philipp Müller + * + * 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 +#include +#include + +GST_DEBUG_CATEGORY_STATIC (gst_ogm_parse_debug); +#define GST_CAT_DEFAULT gst_ogm_parse_debug + +#define GST_TYPE_OGM_VIDEO_PARSE (gst_ogm_video_parse_get_type()) +#define GST_IS_OGM_VIDEO_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_OGM_VIDEO_PARSE)) + +#define GST_TYPE_OGM_AUDIO_PARSE (gst_ogm_audio_parse_get_type()) +#define GST_IS_OGM_AUDIO_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_OGM_AUDIO_PARSE)) + +#define GST_TYPE_OGM_TEXT_PARSE (gst_ogm_text_parse_get_type()) +#define GST_IS_OGM_TEXT_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_OGM_TEXT_PARSE)) + +#define GST_TYPE_OGM_PARSE (gst_ogm_parse_get_type()) +#define GST_OGM_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_OGM_PARSE, GstOgmParse)) +#define GST_OGM_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_OGM_PARSE, GstOgmParse)) +#define GST_IS_OGM_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_OGM_PARSE)) +#define GST_IS_OGM_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_OGM_PARSE)) +#define GST_OGM_PARSE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_OGM_PARSE, GstOgmParseClass)) + +static const GstElementDetails gst_ogm_audio_parse_details = +GST_ELEMENT_DETAILS ("OGM audio stream parser", + "Codec/Decoder/Audio", + "parse an OGM audio header and stream", + "Ronald Bultje "); + +static const GstElementDetails gst_ogm_video_parse_details = +GST_ELEMENT_DETAILS ("OGM video stream parser", + "Codec/Decoder/Video", + "parse an OGM video header and stream", + "Ronald Bultje "); + +static const GstElementDetails gst_ogm_text_parse_details = +GST_ELEMENT_DETAILS ("OGM text stream parser", + "Codec/Decoder/Subtitle", + "parse an OGM text header and stream", + "Ronald Bultje "); + +typedef struct _stream_header_video +{ + gint32 width; + gint32 height; +} stream_header_video; + +typedef struct _stream_header_audio +{ + gint16 channels; + gint16 blockalign; + gint32 avgbytespersec; +} stream_header_audio; + +/* sizeof(stream_header) might differ due to structure packing and + * alignment differences on some architectures, so not using that */ +#define OGM_STREAM_HEADER_SIZE (8+4+4+8+8+4+4+4+8) + +typedef struct _stream_header +{ + gchar streamtype[8]; + gchar subtype[4 + 1]; + + /* size of the structure */ + gint32 size; + + /* in reference time */ + gint64 time_unit; + + gint64 samples_per_unit; + + /* in media time */ + gint32 default_len; + + gint32 buffersize; + gint32 bits_per_sample; + + union + { + stream_header_video video; + stream_header_audio audio; + /* text has no additional data */ + } s; +} stream_header; + +typedef struct _GstOgmParse +{ + GstElement element; + + /* pads */ + GstPad *srcpad, *sinkpad; + GstPadTemplate *srcpadtempl; + + /* we need to cache events that we receive before creating the source pad */ + GList *cached_events; + + /* audio or video */ + stream_header hdr; + + /* expected next granulepos (used for timestamp guessing) */ + guint64 next_granulepos; +} GstOgmParse; + +typedef struct _GstOgmParseClass +{ + GstElementClass parent_class; +} GstOgmParseClass; + +static GstStaticPadTemplate sink_factory_video = +GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-ogm-video")); +static GstStaticPadTemplate sink_factory_audio = +GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-ogm-audio")); +static GstStaticPadTemplate sink_factory_text = +GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-ogm-text")); +static GstPadTemplate *video_src_templ, *audio_src_templ, *text_src_templ; + +static GType gst_ogm_audio_parse_get_type (void); +static GType gst_ogm_video_parse_get_type (void); +static GType gst_ogm_text_parse_get_type (void); +static GType gst_ogm_parse_get_type (void); + +static void gst_ogm_audio_parse_base_init (GstOgmParseClass * klass); +static void gst_ogm_video_parse_base_init (GstOgmParseClass * klass); +static void gst_ogm_text_parse_base_init (GstOgmParseClass * klass); +static void gst_ogm_parse_class_init (GstOgmParseClass * klass); +static void gst_ogm_parse_init (GstOgmParse * ogm); +static void gst_ogm_video_parse_init (GstOgmParse * ogm); +static void gst_ogm_audio_parse_init (GstOgmParse * ogm); +static void gst_ogm_text_parse_init (GstOgmParse * ogm); + +static const GstQueryType *gst_ogm_parse_get_sink_querytypes (GstPad * pad); +static gboolean gst_ogm_parse_sink_event (GstPad * pad, GstEvent * event); +static gboolean gst_ogm_parse_sink_query (GstPad * pad, GstQuery * query); +static gboolean gst_ogm_parse_sink_convert (GstPad * pad, GstFormat src_format, + gint64 src_value, GstFormat * dest_format, gint64 * dest_value); + +static GstFlowReturn gst_ogm_parse_chain (GstPad * pad, GstBuffer * buffer); + +static GstStateChangeReturn gst_ogm_parse_change_state (GstElement * element, + GstStateChange transition); + +static GstElementClass *parent_class = NULL; + +static GType +gst_ogm_parse_get_type (void) +{ + static GType ogm_parse_type = 0; + + if (!ogm_parse_type) { + static const GTypeInfo ogm_parse_info = { + sizeof (GstOgmParseClass), + NULL, + NULL, + (GClassInitFunc) gst_ogm_parse_class_init, + NULL, + NULL, + sizeof (GstOgmParse), + 0, + (GInstanceInitFunc) gst_ogm_parse_init, + }; + + ogm_parse_type = + g_type_register_static (GST_TYPE_ELEMENT, + "GstOgmParse", &ogm_parse_info, 0); + } + + return ogm_parse_type; +} + +static GType +gst_ogm_audio_parse_get_type (void) +{ + static GType ogm_audio_parse_type = 0; + + if (!ogm_audio_parse_type) { + static const GTypeInfo ogm_audio_parse_info = { + sizeof (GstOgmParseClass), + (GBaseInitFunc) gst_ogm_audio_parse_base_init, + NULL, + NULL, + NULL, + NULL, + sizeof (GstOgmParse), + 0, + (GInstanceInitFunc) gst_ogm_audio_parse_init, + }; + + ogm_audio_parse_type = + g_type_register_static (GST_TYPE_OGM_PARSE, + "GstOgmAudioParse", &ogm_audio_parse_info, 0); + } + + return ogm_audio_parse_type; +} + +static GType +gst_ogm_video_parse_get_type (void) +{ + static GType ogm_video_parse_type = 0; + + if (!ogm_video_parse_type) { + static const GTypeInfo ogm_video_parse_info = { + sizeof (GstOgmParseClass), + (GBaseInitFunc) gst_ogm_video_parse_base_init, + NULL, + NULL, + NULL, + NULL, + sizeof (GstOgmParse), + 0, + (GInstanceInitFunc) gst_ogm_video_parse_init, + }; + + ogm_video_parse_type = + g_type_register_static (GST_TYPE_OGM_PARSE, + "GstOgmVideoParse", &ogm_video_parse_info, 0); + } + + return ogm_video_parse_type; +} + +static GType +gst_ogm_text_parse_get_type (void) +{ + static GType ogm_text_parse_type = 0; + + if (!ogm_text_parse_type) { + static const GTypeInfo ogm_text_parse_info = { + sizeof (GstOgmParseClass), + (GBaseInitFunc) gst_ogm_text_parse_base_init, + NULL, + NULL, + NULL, + NULL, + sizeof (GstOgmParse), + 0, + (GInstanceInitFunc) gst_ogm_text_parse_init, + }; + + ogm_text_parse_type = + g_type_register_static (GST_TYPE_OGM_PARSE, + "GstOgmTextParse", &ogm_text_parse_info, 0); + } + + return ogm_text_parse_type; +} + +static void +gst_ogm_audio_parse_base_init (GstOgmParseClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstCaps *caps = gst_riff_create_audio_template_caps (); + + gst_element_class_set_details (element_class, &gst_ogm_audio_parse_details); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_factory_audio)); + audio_src_templ = gst_pad_template_new ("src", + GST_PAD_SRC, GST_PAD_SOMETIMES, caps); + gst_element_class_add_pad_template (element_class, audio_src_templ); +} + +static void +gst_ogm_video_parse_base_init (GstOgmParseClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstCaps *caps = gst_riff_create_video_template_caps (); + + gst_element_class_set_details (element_class, &gst_ogm_video_parse_details); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_factory_video)); + video_src_templ = gst_pad_template_new ("src", + GST_PAD_SRC, GST_PAD_SOMETIMES, caps); + gst_element_class_add_pad_template (element_class, video_src_templ); +} + +static void +gst_ogm_text_parse_base_init (GstOgmParseClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstCaps *caps = gst_caps_new_simple ("text/plain", NULL, NULL); + + gst_element_class_set_details (element_class, &gst_ogm_text_parse_details); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_factory_text)); + text_src_templ = gst_pad_template_new ("src", + GST_PAD_SRC, GST_PAD_SOMETIMES, caps); + gst_element_class_add_pad_template (element_class, text_src_templ); +} + +static void +gst_ogm_parse_class_init (GstOgmParseClass * klass) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_ogm_parse_change_state); +} + +static void +gst_ogm_parse_init (GstOgmParse * ogm) +{ + memset (&ogm->hdr, 0, sizeof (ogm->hdr)); + ogm->next_granulepos = 0; + ogm->srcpad = NULL; + ogm->cached_events = NULL; +} + +static void +gst_ogm_audio_parse_init (GstOgmParse * ogm) +{ + ogm->sinkpad = gst_pad_new_from_static_template (&sink_factory_audio, "sink"); + gst_pad_set_query_function (ogm->sinkpad, + GST_DEBUG_FUNCPTR (gst_ogm_parse_sink_query)); + gst_pad_set_chain_function (ogm->sinkpad, + GST_DEBUG_FUNCPTR (gst_ogm_parse_chain)); + gst_pad_set_event_function (ogm->sinkpad, + GST_DEBUG_FUNCPTR (gst_ogm_parse_sink_event)); + gst_element_add_pad (GST_ELEMENT (ogm), ogm->sinkpad); + + ogm->srcpad = NULL; + ogm->srcpadtempl = audio_src_templ; +} + +static void +gst_ogm_video_parse_init (GstOgmParse * ogm) +{ + ogm->sinkpad = gst_pad_new_from_static_template (&sink_factory_video, "sink"); + gst_pad_set_query_function (ogm->sinkpad, + GST_DEBUG_FUNCPTR (gst_ogm_parse_sink_query)); + gst_pad_set_chain_function (ogm->sinkpad, + GST_DEBUG_FUNCPTR (gst_ogm_parse_chain)); + gst_pad_set_event_function (ogm->sinkpad, + GST_DEBUG_FUNCPTR (gst_ogm_parse_sink_event)); + gst_element_add_pad (GST_ELEMENT (ogm), ogm->sinkpad); + + ogm->srcpad = NULL; + ogm->srcpadtempl = video_src_templ; +} + +static void +gst_ogm_text_parse_init (GstOgmParse * ogm) +{ + ogm->sinkpad = gst_pad_new_from_static_template (&sink_factory_text, "sink"); + gst_pad_set_query_type_function (ogm->sinkpad, + gst_ogm_parse_get_sink_querytypes); + gst_pad_set_query_function (ogm->sinkpad, + GST_DEBUG_FUNCPTR (gst_ogm_parse_sink_query)); + gst_pad_set_chain_function (ogm->sinkpad, + GST_DEBUG_FUNCPTR (gst_ogm_parse_chain)); + gst_pad_set_event_function (ogm->sinkpad, + GST_DEBUG_FUNCPTR (gst_ogm_parse_sink_event)); + gst_element_add_pad (GST_ELEMENT (ogm), ogm->sinkpad); + + ogm->srcpad = NULL; + ogm->srcpadtempl = text_src_templ; +} + +static const GstQueryType * +gst_ogm_parse_get_sink_querytypes (GstPad * pad) +{ + static const GstQueryType types[] = { + GST_QUERY_POSITION, + 0 + }; + + return types; +} + +static gboolean +gst_ogm_parse_sink_convert (GstPad * pad, + GstFormat src_format, gint64 src_value, + GstFormat * dest_format, gint64 * dest_value) +{ + gboolean res = FALSE; + GstOgmParse *ogm = GST_OGM_PARSE (gst_pad_get_parent (pad)); + + switch (src_format) { + case GST_FORMAT_DEFAULT: + switch (*dest_format) { + case GST_FORMAT_TIME: + switch (ogm->hdr.streamtype[0]) { + case 'a': + *dest_value = GST_SECOND * src_value / ogm->hdr.samples_per_unit; + res = TRUE; + break; + case 'v': + case 't': + *dest_value = (GST_SECOND / 10000000) * + ogm->hdr.time_unit * src_value; + res = TRUE; + break; + default: + break; + } + break; + default: + break; + } + break; + case GST_FORMAT_TIME: + switch (*dest_format) { + case GST_FORMAT_DEFAULT: + switch (ogm->hdr.streamtype[0]) { + case 'a': + *dest_value = ogm->hdr.samples_per_unit * src_value / GST_SECOND; + res = TRUE; + break; + case 'v': + case 't': + *dest_value = src_value / + ((GST_SECOND / 10000000) * ogm->hdr.time_unit); + res = TRUE; + break; + default: + break; + } + break; + default: + break; + } + break; + default: + break; + } + + gst_object_unref (ogm); + return res; +} + +static gboolean +gst_ogm_parse_sink_query (GstPad * pad, GstQuery * query) +{ + GstOgmParse *ogm = GST_OGM_PARSE (gst_pad_get_parent (pad)); + GstFormat format; + gboolean res = FALSE; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_POSITION: + { + gint64 val; + + gst_query_parse_position (query, &format, NULL); + + if (format != GST_FORMAT_DEFAULT && format != GST_FORMAT_TIME) + break; + + if ((res = gst_ogm_parse_sink_convert (pad, + GST_FORMAT_DEFAULT, ogm->next_granulepos, &format, &val))) { + /* don't know the total length here.. */ + gst_query_set_position (query, format, val); + } + break; + } + case GST_QUERY_CONVERT: + { + GstFormat src_fmt, dest_fmt; + gint64 src_val, dest_val; + + /* peel off input */ + gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val); + if ((res = gst_ogm_parse_sink_convert (pad, src_fmt, src_val, + &dest_fmt, &dest_val))) { + gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val); + } + break; + } + default: + res = gst_pad_query_default (pad, query); + break; + } + + gst_object_unref (ogm); + return res; +} + +static GstFlowReturn +gst_ogm_parse_stream_header (GstOgmParse * ogm, const guint8 * data, guint size) +{ + GstCaps *caps = NULL; + + /* stream header */ + if (size < OGM_STREAM_HEADER_SIZE) + goto buffer_too_small; + + if (!memcmp (data, "video\000\000\000", 8)) { + ogm->hdr.s.video.width = GST_READ_UINT32_LE (&data[44]); + ogm->hdr.s.video.height = GST_READ_UINT32_LE (&data[48]); + } else if (!memcmp (data, "audio\000\000\000", 8)) { + ogm->hdr.s.audio.channels = GST_READ_UINT32_LE (&data[44]); + ogm->hdr.s.audio.blockalign = GST_READ_UINT32_LE (&data[46]); + ogm->hdr.s.audio.avgbytespersec = GST_READ_UINT32_LE (&data[48]); + } else if (!memcmp (data, "text\000\000\000\000", 8)) { + /* nothing here */ + } else { + goto cannot_decode; + } + memcpy (ogm->hdr.streamtype, &data[0], 8); + memcpy (ogm->hdr.subtype, &data[8], 4); + ogm->hdr.subtype[4] = '\0'; + ogm->hdr.size = GST_READ_UINT32_LE (&data[12]); + ogm->hdr.time_unit = GST_READ_UINT64_LE (&data[16]); + ogm->hdr.samples_per_unit = GST_READ_UINT64_LE (&data[24]); + ogm->hdr.default_len = GST_READ_UINT32_LE (&data[32]); + ogm->hdr.buffersize = GST_READ_UINT32_LE (&data[36]); + ogm->hdr.bits_per_sample = GST_READ_UINT32_LE (&data[40]); + + switch (ogm->hdr.streamtype[0]) { + case 'a':{ + guint codec_id = 0; + + if (sscanf (ogm->hdr.subtype, "%04x", &codec_id) != 1) { + GST_WARNING_OBJECT (ogm, "cannot parse subtype %s", ogm->hdr.subtype); + } + + caps = + gst_riff_create_audio_caps (codec_id, NULL, NULL, NULL, NULL, NULL); + + if (caps == NULL) { + GST_WARNING_OBJECT (ogm, "no audio caps for codec %u found", codec_id); + caps = gst_caps_new_simple ("audio/x-ogm-unknown", "codec_id", + G_TYPE_INT, (gint) codec_id, NULL); + } + + gst_caps_set_simple (caps, + "channels", G_TYPE_INT, ogm->hdr.s.audio.channels, + "rate", G_TYPE_INT, ogm->hdr.samples_per_unit, NULL); + + GST_LOG_OBJECT (ogm, "Type: %s, subtype: 0x%04x, channels: %d, " + "samplerate: %d, blockalign: %d, bps: %d, caps = %" GST_PTR_FORMAT, + ogm->hdr.streamtype, codec_id, ogm->hdr.s.audio.channels, + (gint) ogm->hdr.samples_per_unit, ogm->hdr.s.audio.blockalign, + ogm->hdr.s.audio.avgbytespersec, caps); + break; + } + case 'v':{ + guint32 fourcc; + + fourcc = GST_MAKE_FOURCC (ogm->hdr.subtype[0], + ogm->hdr.subtype[1], ogm->hdr.subtype[2], ogm->hdr.subtype[3]); + + caps = gst_riff_create_video_caps (fourcc, NULL, NULL, NULL, NULL, NULL); + + if (caps == NULL) { + GST_WARNING_OBJECT (ogm, "could not find video caps for fourcc %" + GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc)); + caps = gst_caps_new_simple ("video/x-ogm-unknown", "fourcc", + GST_TYPE_FOURCC, fourcc, NULL); + break; + } + + GST_LOG_OBJECT (ogm, "Type: %s, subtype: %" GST_FOURCC_FORMAT + ", size: %dx%d, timeunit: %" G_GINT64_FORMAT + " (fps: %lf), s/u: %" G_GINT64_FORMAT ", " + "def.len: %d, bufsize: %d, bps: %d, caps = %" GST_PTR_FORMAT, + ogm->hdr.streamtype, GST_FOURCC_ARGS (fourcc), + ogm->hdr.s.video.width, ogm->hdr.s.video.height, + ogm->hdr.time_unit, 10000000. / ogm->hdr.time_unit, + ogm->hdr.samples_per_unit, ogm->hdr.default_len, + ogm->hdr.buffersize, ogm->hdr.bits_per_sample, caps); + + gst_caps_set_simple (caps, + "width", G_TYPE_INT, ogm->hdr.s.video.width, + "height", G_TYPE_INT, ogm->hdr.s.video.height, + "framerate", GST_TYPE_FRACTION, 10000000, ogm->hdr.time_unit, NULL); + break; + } + case 't':{ + GST_LOG_OBJECT (ogm, "Type: %s, s/u: %" G_GINT64_FORMAT + ", timeunit=%" G_GINT64_FORMAT, + ogm->hdr.streamtype, ogm->hdr.samples_per_unit, ogm->hdr.time_unit); + caps = gst_caps_new_simple ("text/plain", NULL); + break; + } + default: + g_assert_not_reached (); + } + + if (caps == NULL) + goto cannot_decode; + + if (ogm->srcpad) { + GstCaps *current_caps = GST_PAD_CAPS (ogm->srcpad); + + if (current_caps && caps && !gst_caps_is_equal (current_caps, caps)) { + GST_WARNING_OBJECT (ogm, "Already an existing pad %s:%s", + GST_DEBUG_PAD_NAME (ogm->srcpad)); + gst_pad_set_active (ogm->srcpad, FALSE); + gst_element_remove_pad (GST_ELEMENT (ogm), ogm->srcpad); + ogm->srcpad = NULL; + } else { + GST_DEBUG_OBJECT (ogm, "Existing pad has the same caps, do nothing"); + } + } + + if (ogm->srcpad == NULL) { + GList *l, *cached_events; + + ogm->srcpad = gst_pad_new_from_template (ogm->srcpadtempl, "src"); + gst_pad_use_fixed_caps (ogm->srcpad); + gst_pad_set_caps (ogm->srcpad, caps); + gst_pad_set_active (ogm->srcpad, TRUE); + gst_element_add_pad (GST_ELEMENT (ogm), ogm->srcpad); + GST_INFO_OBJECT (ogm, "Added pad %s:%s with caps %" GST_PTR_FORMAT, + GST_DEBUG_PAD_NAME (ogm->srcpad), caps); + + GST_OBJECT_LOCK (ogm); + cached_events = ogm->cached_events; + ogm->cached_events = NULL; + GST_OBJECT_UNLOCK (ogm); + + for (l = cached_events; l; l = l->next) { + GstEvent *event = GST_EVENT_CAST (l->data); + + GST_DEBUG_OBJECT (ogm, "Pushing cached event %" GST_PTR_FORMAT, event); + gst_pad_push_event (ogm->srcpad, event); + } + g_list_free (cached_events); + } + + gst_caps_unref (caps); + + return GST_FLOW_OK; + +/* ERRORS */ +buffer_too_small: + { + GST_ELEMENT_ERROR (ogm, STREAM, WRONG_TYPE, ("Buffer too small"), (NULL)); + return GST_FLOW_ERROR; + } +cannot_decode: + { + GST_ELEMENT_ERROR (ogm, STREAM, DECODE, (NULL), ("unknown ogm format")); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_ogm_parse_comment_packet (GstOgmParse * ogm, GstBuffer * buf) +{ + GstFlowReturn ret; + + if (ogm->srcpad == NULL) { + GST_DEBUG ("no source pad"); + return GST_FLOW_WRONG_STATE; + } + + /* if this is not a subtitle stream, push the vorbiscomment packet + * on downstream, the respective decoder will handle it; if it is + * a subtitle stream, we will have to handle the comment ourself */ + if (ogm->hdr.streamtype[0] == 't') { + GstTagList *tags; + + tags = gst_tag_list_from_vorbiscomment_buffer (buf, + (guint8 *) "\003vorbis", 7, NULL); + + if (tags) { + GST_DEBUG_OBJECT (ogm, "tags = %" GST_PTR_FORMAT, tags); + gst_element_found_tags_for_pad (GST_ELEMENT (ogm), ogm->srcpad, tags); + } else { + GST_DEBUG_OBJECT (ogm, "failed to extract tags from vorbis comment"); + } + /* do not push packet downstream, just let parent unref it */ + ret = GST_FLOW_OK; + } else { + buf = gst_buffer_copy (buf); + gst_buffer_set_caps (buf, GST_PAD_CAPS (ogm->srcpad)); + ret = gst_pad_push (ogm->srcpad, buf); + } + + return ret; +} + +static void +gst_ogm_text_parse_strip_trailing_zeroes (GstOgmParse * ogm, GstBuffer * buf) +{ + const guint8 *data; + guint size; + + g_assert (gst_buffer_is_metadata_writable (buf)); + + /* zeroes are not valid UTF-8 characters, so strip them from output */ + data = GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + while (size > 0 && data[size - 1] == '\0') { + --size; + } + + GST_BUFFER_SIZE (buf) = size; +} + +static GstFlowReturn +gst_ogm_parse_data_packet (GstOgmParse * ogm, GstBuffer * buf) +{ + GstFlowReturn ret; + const guint8 *data; + GstBuffer *sbuf; + gboolean keyframe; + guint size, len, n, xsize = 0; + + data = GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + + if ((data[0] & 0x01) != 0) + goto invalid_startcode; + + /* data - push on */ + len = ((data[0] & 0xc0) >> 6) | ((data[0] & 0x02) << 1); + keyframe = (((data[0] & 0x08) >> 3) != 0); + + if ((1 + len) > size) + goto buffer_too_small; + + for (n = len; n > 0; n--) { + xsize = (xsize << 8) | data[n]; + } + + GST_LOG_OBJECT (ogm, "[0x%02x] samples: %d, hdrbytes: %d, datasize: %d", + data[0], xsize, len, size - len - 1); + + sbuf = gst_buffer_create_sub (buf, len + 1, size - len - 1); + + if (GST_BUFFER_OFFSET_END_IS_VALID (buf)) + ogm->next_granulepos = GST_BUFFER_OFFSET_END (buf); + + switch (ogm->hdr.streamtype[0]) { + case 't': + case 'v':{ + GstClockTime ts, next_ts; + guint samples; + + samples = (ogm->hdr.streamtype[0] == 'v') ? 1 : xsize; + + if (!keyframe) { + GST_BUFFER_FLAG_SET (sbuf, GST_BUFFER_FLAG_DELTA_UNIT); + } + + /* shouldn't this be granulepos - samples? (tpm) */ + ts = gst_util_uint64_scale (ogm->next_granulepos, + ogm->hdr.time_unit * GST_SECOND, 10000000); + next_ts = gst_util_uint64_scale (ogm->next_granulepos + samples, + ogm->hdr.time_unit * GST_SECOND, 10000000); + + GST_BUFFER_TIMESTAMP (sbuf) = ts; + GST_BUFFER_DURATION (sbuf) = next_ts - ts; + + ogm->next_granulepos += samples; + + if (ogm->hdr.streamtype[0] == 't') { + gst_ogm_text_parse_strip_trailing_zeroes (ogm, sbuf); + } + break; + } + case 'a':{ + GstClockTime ts, next_ts; + + /* shouldn't this be granulepos - samples? (tpm) */ + ts = gst_util_uint64_scale_int (ogm->next_granulepos, + GST_SECOND, ogm->hdr.samples_per_unit); + next_ts = gst_util_uint64_scale_int (ogm->next_granulepos + xsize, + GST_SECOND, ogm->hdr.samples_per_unit); + + GST_BUFFER_TIMESTAMP (sbuf) = ts; + GST_BUFFER_DURATION (sbuf) = next_ts - ts; + + ogm->next_granulepos += xsize; + break; + } + default: + g_assert_not_reached (); + break; + } + + if (ogm->srcpad) { + gst_buffer_set_caps (sbuf, GST_PAD_CAPS (ogm->srcpad)); + GST_LOG_OBJECT (ogm, "Pushing buffer with ts=%" GST_TIME_FORMAT, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (sbuf))); + ret = gst_pad_push (ogm->srcpad, sbuf); + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (ogm, "Flow on %s:%s = %s", + GST_DEBUG_PAD_NAME (ogm->srcpad), gst_flow_get_name (ret)); + } + } else { + ret = GST_FLOW_WRONG_STATE; + } + + return ret; + +/* ERRORS */ +invalid_startcode: + { + GST_ELEMENT_ERROR (ogm, STREAM, DECODE, (NULL), + ("unexpected packet startcode 0x%02x", data[0])); + return GST_FLOW_ERROR; + } +buffer_too_small: + { + GST_ELEMENT_ERROR (ogm, STREAM, DECODE, (NULL), + ("buffer too small, len+1=%u, size=%u", len + 1, size)); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_ogm_parse_chain (GstPad * pad, GstBuffer * buf) +{ + GstFlowReturn ret = GST_FLOW_OK; + GstOgmParse *ogm = GST_OGM_PARSE (GST_PAD_PARENT (pad)); + guint8 *data = GST_BUFFER_DATA (buf); + guint size = GST_BUFFER_SIZE (buf); + + if (size < 1) + goto buffer_too_small; + + GST_LOG_OBJECT (ogm, "Packet with start code 0x%02x", data[0]); + + switch (data[0]) { + case 0x01:{ + ret = gst_ogm_parse_stream_header (ogm, data + 1, size - 1); + break; + } + case 0x03:{ + ret = gst_ogm_parse_comment_packet (ogm, buf); + break; + } + default:{ + ret = gst_ogm_parse_data_packet (ogm, buf); + break; + } + } + + gst_buffer_unref (buf); + + if (ret != GST_FLOW_OK) { + GST_DEBUG_OBJECT (ogm, "Flow: %s", gst_flow_get_name (ret)); + } + + return ret; + +/* ERRORS */ +buffer_too_small: + { + GST_ELEMENT_ERROR (ogm, STREAM, DECODE, (NULL), ("buffer too small")); + gst_buffer_unref (buf); + return GST_FLOW_ERROR; + } +} + +static gboolean +gst_ogm_parse_sink_event (GstPad * pad, GstEvent * event) +{ + GstOgmParse *ogm = GST_OGM_PARSE (gst_pad_get_parent (pad)); + gboolean res; + + GST_LOG_OBJECT (ogm, "processing %s event", GST_EVENT_TYPE_NAME (event)); + + GST_OBJECT_LOCK (ogm); + if (ogm->srcpad == NULL) { + ogm->cached_events = g_list_append (ogm->cached_events, event); + GST_OBJECT_UNLOCK (ogm); + res = TRUE; + } else { + GST_OBJECT_UNLOCK (ogm); + res = gst_pad_event_default (pad, event); + } + + gst_object_unref (ogm); + return res; +} + +static GstStateChangeReturn +gst_ogm_parse_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstOgmParse *ogm = GST_OGM_PARSE (element); + + ret = parent_class->change_state (element, transition); + if (ret != GST_STATE_CHANGE_SUCCESS) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + if (ogm->srcpad) { + gst_pad_set_active (ogm->srcpad, FALSE); + gst_element_remove_pad (element, ogm->srcpad); + ogm->srcpad = NULL; + } + memset (&ogm->hdr, 0, sizeof (ogm->hdr)); + ogm->next_granulepos = 0; + g_list_foreach (ogm->cached_events, (GFunc) gst_mini_object_unref, NULL); + g_list_free (ogm->cached_events); + ogm->cached_events = NULL; + break; + default: + break; + } + + return ret; +} + +gboolean +gst_ogm_parse_plugin_init (GstPlugin * plugin) +{ + gst_riff_init (); + + GST_DEBUG_CATEGORY_INIT (gst_ogm_parse_debug, "ogmparse", 0, "ogm parser"); + + return gst_element_register (plugin, "ogmaudioparse", GST_RANK_PRIMARY, + GST_TYPE_OGM_AUDIO_PARSE) && + gst_element_register (plugin, "ogmvideoparse", GST_RANK_PRIMARY, + GST_TYPE_OGM_VIDEO_PARSE) && + gst_element_register (plugin, "ogmtextparse", GST_RANK_PRIMARY, + GST_TYPE_OGM_TEXT_PARSE); +}