diff --git a/ChangeLog b/ChangeLog index 8ee24d9759..2aca2fed84 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,22 @@ +2006-08-17 Wim Taymans + + * ext/ogg/Makefile.am: + * ext/ogg/gstogg.c: (plugin_init): + * ext/ogg/gstoggaviparse.c: (gst_ogg_avi_parse_get_type), + (gst_ogg_avi_parse_base_init), (gst_ogg_avi_parse_class_init), + (gst_ogg_avi_parse_init), (gst_ogg_avi_parse_finalize), + (gst_ogg_avi_parse_setcaps), (gst_ogg_avi_parse_event), + (gst_ogg_avi_parse_push_packet), (gst_ogg_avi_parse_chain), + (gst_ogg_avi_parse_change_state), (gst_ogg_avi_parse_plugin_init): + Added ogg-in-avi parser element. Fixes #140139. + + * ext/ogg/gstoggmux.c: (gst_ogg_mux_buffer_from_page): + Fixed a bug in oggdemux debug code. + + * gst-libs/gst/riff/riff-media.c: (gst_riff_create_audio_caps), + (gst_riff_create_audio_template_caps): + Recognise Ogg in the AVI extensible wave format. + 2006-08-17 Tim-Philipp Müller * gst-libs/gst/cdda/gstcddabasesrc.c: (gst_cdda_base_src_create): diff --git a/ext/ogg/Makefile.am b/ext/ogg/Makefile.am index 174712c696..fcf1048ad3 100644 --- a/ext/ogg/Makefile.am +++ b/ext/ogg/Makefile.am @@ -5,6 +5,7 @@ libgstogg_la_SOURCES = \ gstoggdemux.c \ gstoggmux.c \ gstogmparse.c \ + gstoggaviparse.c \ gstoggparse.c libgstogg_la_CFLAGS = $(GST_CFLAGS) $(OGG_CFLAGS) diff --git a/ext/ogg/gstogg.c b/ext/ogg/gstogg.c index 3220e83157..62f8582d23 100644 --- a/ext/ogg/gstogg.c +++ b/ext/ogg/gstogg.c @@ -27,6 +27,7 @@ extern gboolean gst_ogg_demux_plugin_init (GstPlugin * plugin); extern gboolean gst_ogg_mux_plugin_init (GstPlugin * plugin); extern gboolean gst_ogm_parse_plugin_init (GstPlugin * plugin); extern gboolean gst_ogg_parse_plugin_init (GstPlugin * plugin); +extern gboolean gst_ogg_avi_parse_plugin_init (GstPlugin * plugin); GST_DEBUG_CATEGORY (vorbisdec_debug); @@ -37,6 +38,7 @@ plugin_init (GstPlugin * plugin) gst_ogg_mux_plugin_init (plugin); gst_ogm_parse_plugin_init (plugin); gst_ogg_parse_plugin_init (plugin); + gst_ogg_avi_parse_plugin_init (plugin); return TRUE; } diff --git a/ext/ogg/gstoggaviparse.c b/ext/ogg/gstoggaviparse.c new file mode 100644 index 0000000000..9fa0701e45 --- /dev/null +++ b/ext/ogg/gstoggaviparse.c @@ -0,0 +1,478 @@ +/* GStreamer + * Copyright (C) 2006 Wim Taymans + * + * gstoggaviparse.c: ogg avi stream parser + * + * 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. + */ + +/* + * Ogg in AVI is mostly done for vorbis audio. In the codec_data we receive the + * first 3 packets of the raw vorbis data. On the sinkpad we receive full-blown Ogg + * pages. + * Before extracting the packets out of the ogg pages, we push the raw vorbis + * header packets to the decoder. + * We don't use the incomming timestamps but use the ganulepos on the ogg pages + * directly. + * This parser only does ogg/vorbis for now. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include + +static const GstElementDetails gst_ogg_avi_parse_details = +GST_ELEMENT_DETAILS ("Ogg AVI parser", + "Codec/Parser", + "parse an ogg avi stream into pages (info about ogg: http://xiph.org)", + "Wim Taymans "); + +GST_DEBUG_CATEGORY_STATIC (gst_ogg_avi_parse_debug); +#define GST_CAT_DEFAULT gst_ogg_avi_parse_debug + +#define GST_TYPE_OGG_AVI_PARSE (gst_ogg_avi_parse_get_type()) +#define GST_OGG_AVI_PARSE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OGG_AVI_PARSE, GstOggAviParse)) +#define GST_OGG_AVI_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OGG_AVI_PARSE, GstOggAviParse)) +#define GST_IS_OGG_AVI_PARSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OGG_AVI_PARSE)) +#define GST_IS_OGG_AVI_PARSE_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OGG_AVI_PARSE)) + +static GType gst_ogg_avi_parse_get_type (void); + +typedef struct _GstOggAviParse GstOggAviParse; +typedef struct _GstOggAviParseClass GstOggAviParseClass; + +struct _GstOggAviParse +{ + GstElement element; + + GstPad *sinkpad; + GstPad *srcpad; + + gboolean discont; + gint serial; + + ogg_sync_state sync; + ogg_stream_state stream; +}; + +struct _GstOggAviParseClass +{ + GstElementClass parent_class; +}; + +static void gst_ogg_avi_parse_base_init (gpointer g_class); +static void gst_ogg_avi_parse_class_init (GstOggAviParseClass * klass); +static void gst_ogg_avi_parse_init (GstOggAviParse * ogg); +static GstElementClass *parent_class = NULL; + +static GType +gst_ogg_avi_parse_get_type (void) +{ + static GType ogg_avi_parse_type = 0; + + if (!ogg_avi_parse_type) { + static const GTypeInfo ogg_avi_parse_info = { + sizeof (GstOggAviParseClass), + gst_ogg_avi_parse_base_init, + NULL, + (GClassInitFunc) gst_ogg_avi_parse_class_init, + NULL, + NULL, + sizeof (GstOggAviParse), + 0, + (GInstanceInitFunc) gst_ogg_avi_parse_init, + }; + + ogg_avi_parse_type = + g_type_register_static (GST_TYPE_ELEMENT, "GstOggAviParse", + &ogg_avi_parse_info, 0); + } + return ogg_avi_parse_type; +} + +enum +{ + PROP_0 +}; + +static GstStaticPadTemplate ogg_avi_parse_src_template_factory = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-vorbis") + ); + +static GstStaticPadTemplate ogg_avi_parse_sink_template_factory = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-ogg-avi") + ); + +static void gst_ogg_avi_parse_finalize (GObject * object); +static GstStateChangeReturn gst_ogg_avi_parse_change_state (GstElement * + element, GstStateChange transition); +static gboolean gst_ogg_avi_parse_event (GstPad * pad, GstEvent * event); +static GstFlowReturn gst_ogg_avi_parse_chain (GstPad * pad, GstBuffer * buffer); +static gboolean gst_ogg_avi_parse_setcaps (GstPad * pad, GstCaps * caps); + +static void +gst_ogg_avi_parse_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details (element_class, &gst_ogg_avi_parse_details); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&ogg_avi_parse_sink_template_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&ogg_avi_parse_src_template_factory)); +} + +static void +gst_ogg_avi_parse_class_init (GstOggAviParseClass * klass) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + gstelement_class->change_state = gst_ogg_avi_parse_change_state; + + gobject_class->finalize = gst_ogg_avi_parse_finalize; +} + +static void +gst_ogg_avi_parse_init (GstOggAviParse * ogg) +{ + /* create the sink and source pads */ + ogg->sinkpad = + gst_pad_new_from_static_template (&ogg_avi_parse_sink_template_factory, + "sink"); + gst_pad_set_setcaps_function (ogg->sinkpad, gst_ogg_avi_parse_setcaps); + gst_pad_set_event_function (ogg->sinkpad, gst_ogg_avi_parse_event); + gst_pad_set_chain_function (ogg->sinkpad, gst_ogg_avi_parse_chain); + gst_element_add_pad (GST_ELEMENT (ogg), ogg->sinkpad); + + ogg->srcpad = + gst_pad_new_from_static_template (&ogg_avi_parse_src_template_factory, + "src"); + gst_pad_use_fixed_caps (ogg->srcpad); + gst_element_add_pad (GST_ELEMENT (ogg), ogg->srcpad); +} + +static void +gst_ogg_avi_parse_finalize (GObject * object) +{ + GstOggAviParse *ogg = GST_OGG_AVI_PARSE (object); + + GST_LOG_OBJECT (ogg, "Disposing of object %p", ogg); + + ogg_sync_clear (&ogg->sync); + ogg_stream_clear (&ogg->stream); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_ogg_avi_parse_setcaps (GstPad * pad, GstCaps * caps) +{ + GstOggAviParse *ogg; + GstStructure *structure; + const GValue *codec_data; + GstBuffer *buffer; + guint8 *data; + guint size; + guint32 sizes[3]; + GstCaps *outcaps; + gint i, offs; + + ogg = GST_OGG_AVI_PARSE (GST_OBJECT_PARENT (pad)); + + structure = gst_caps_get_structure (caps, 0); + + /* take codec data */ + codec_data = gst_structure_get_value (structure, "codec_data"); + if (codec_data == NULL) + goto no_data; + + /* only buffers are valid */ + if (G_VALUE_TYPE (codec_data) != GST_TYPE_BUFFER) + goto wrong_format; + + /* Now parse the data */ + buffer = gst_value_get_buffer (codec_data); + + /* first 22 bytes are bits_per_sample, channel_mask, GUID + * Then we get 3 LE guint32 with the 3 header sizes + * then we get the bytes of the 3 headers. */ + data = GST_BUFFER_DATA (buffer); + size = GST_BUFFER_SIZE (buffer); + + GST_LOG_OBJECT (ogg, "configuring codec_data of size %u", size); + + /* skip headers */ + data += 22; + size -= 22; + + /* we need at least 12 bytes for the packet sizes of the 3 headers */ + if (size < 12) + goto buffer_too_small; + + /* read sizes of the 3 headers */ + sizes[0] = GST_READ_UINT32_LE (data); + sizes[1] = GST_READ_UINT32_LE (data + 4); + sizes[2] = GST_READ_UINT32_LE (data + 8); + + GST_DEBUG_OBJECT (ogg, "header sizes: %u %u %u", sizes[0], sizes[1], + sizes[2]); + + data += 12; + size -= 12; + + /* and we need at least enough data for all the headers */ + if (size < sizes[0] + sizes[1] + sizes[2]) + goto buffer_too_small; + + /* set caps */ + outcaps = gst_caps_new_simple ("audio/x-vorbis", NULL); + gst_pad_set_caps (ogg->srcpad, outcaps); + + /* copy header data */ + offs = 34; + for (i = 0; i < 3; i++) { + GstBuffer *out; + + /* now output the raw vorbis header packets */ + out = gst_buffer_create_sub (buffer, offs, sizes[i]); + gst_buffer_set_caps (out, outcaps); + gst_pad_push (ogg->srcpad, out); + + offs += sizes[i]; + } + gst_caps_unref (outcaps); + + return TRUE; + + /* ERRORS */ +no_data: + { + GST_DEBUG_OBJECT (ogg, "no codec_data found in caps"); + return FALSE; + } +wrong_format: + { + GST_DEBUG_OBJECT (ogg, "codec_data is not a buffer"); + return FALSE; + } +buffer_too_small: + { + GST_DEBUG_OBJECT (ogg, "codec_data is too small"); + return FALSE; + } +} + +static gboolean +gst_ogg_avi_parse_event (GstPad * pad, GstEvent * event) +{ + GstOggAviParse *ogg; + gboolean ret; + + ogg = GST_OGG_AVI_PARSE (GST_OBJECT_PARENT (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + ret = gst_pad_push_event (ogg->srcpad, event); + break; + case GST_EVENT_FLUSH_STOP: + ogg_sync_reset (&ogg->sync); + ogg_stream_reset (&ogg->stream); + ogg->discont = TRUE; + ret = gst_pad_push_event (ogg->srcpad, event); + break; + default: + ret = gst_pad_push_event (ogg->srcpad, event); + break; + } + return ret; +} + +static GstFlowReturn +gst_ogg_avi_parse_push_packet (GstOggAviParse * ogg, ogg_packet * packet) +{ + GstBuffer *buffer; + GstFlowReturn result; + + /* allocate space for header and body */ + buffer = gst_buffer_new_and_alloc (packet->bytes); + memcpy (GST_BUFFER_DATA (buffer), packet->packet, packet->bytes); + + GST_LOG_OBJECT (ogg, "created buffer %p from page", buffer); + + GST_BUFFER_OFFSET_END (buffer) = packet->granulepos; + + if (ogg->discont) { + GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); + ogg->discont = FALSE; + } + + result = gst_pad_push (ogg->srcpad, buffer); + + return result; +} + +static GstFlowReturn +gst_ogg_avi_parse_chain (GstPad * pad, GstBuffer * buffer) +{ + GstFlowReturn result = GST_FLOW_OK; + GstOggAviParse *ogg; + guint8 *data; + guint size; + gchar *oggbuf; + gint ret = -1; + + ogg = GST_OGG_AVI_PARSE (GST_OBJECT_PARENT (pad)); + + data = GST_BUFFER_DATA (buffer); + size = GST_BUFFER_SIZE (buffer); + + GST_LOG_OBJECT (ogg, "Chain function received buffer of size %d", size); + + if (GST_BUFFER_IS_DISCONT (buffer)) { + ogg_sync_reset (&ogg->sync); + ogg->discont = TRUE; + } + + /* write data to sync layer */ + oggbuf = ogg_sync_buffer (&ogg->sync, size); + memcpy (oggbuf, data, size); + ogg_sync_wrote (&ogg->sync, size); + gst_buffer_unref (buffer); + + /* try to get as many packets out of the stream as possible */ + do { + ogg_page page; + + /* try to swap out a page */ + ret = ogg_sync_pageout (&ogg->sync, &page); + if (ret == 0) { + GST_DEBUG_OBJECT (ogg, "need more data"); + break; + } else if (ret == -1) { + GST_DEBUG_OBJECT (ogg, "discont in pages"); + ogg->discont = TRUE; + } else { + /* new unknown stream, init the ogg stream with the serial number of the + * page. */ + if (ogg->serial == -1) { + ogg->serial = ogg_page_serialno (&page); + ogg_stream_init (&ogg->stream, ogg->serial); + } + + /* submit page */ + if (ogg_stream_pagein (&ogg->stream, &page) != 0) { + GST_WARNING_OBJECT (ogg, "ogg stream choked on page resetting stream"); + ogg_sync_reset (&ogg->sync); + ogg->discont = TRUE; + continue; + } + + /* try to get as many packets as possible out of the page */ + do { + ogg_packet packet; + + ret = ogg_stream_packetout (&ogg->stream, &packet); + GST_LOG_OBJECT (ogg, "packetout gave %d", ret); + switch (ret) { + case 0: + break; + case -1: + /* out of sync, We mark a DISCONT. */ + ogg->discont = TRUE; + break; + case 1: + result = gst_ogg_avi_parse_push_packet (ogg, &packet); + if (GST_FLOW_IS_FATAL (result)) + goto done; + break; + default: + GST_WARNING_OBJECT (ogg, + "invalid return value %d for ogg_stream_packetout, resetting stream", + ret); + break; + } + } + while (ret != 0); + } + } + while (ret != 0); + +done: + return result; +} + +static GstStateChangeReturn +gst_ogg_avi_parse_change_state (GstElement * element, GstStateChange transition) +{ + GstOggAviParse *ogg; + GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE; + + ogg = GST_OGG_AVI_PARSE (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + ogg_sync_init (&ogg->sync); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + ogg_sync_reset (&ogg->sync); + ogg_stream_reset (&ogg->stream); + ogg->serial = -1; + ogg->discont = TRUE; + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + default: + break; + } + + result = parent_class->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_NULL: + ogg_sync_clear (&ogg->sync); + break; + default: + break; + } + return result; +} + +gboolean +gst_ogg_avi_parse_plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_ogg_avi_parse_debug, "oggaviparse", 0, + "ogg avi parser"); + + return gst_element_register (plugin, "oggaviparse", GST_RANK_PRIMARY, + GST_TYPE_OGG_AVI_PARSE); +} diff --git a/ext/ogg/gstoggmux.c b/ext/ogg/gstoggmux.c index 0ec8d99837..0be970ac64 100644 --- a/ext/ogg/gstoggmux.c +++ b/ext/ogg/gstoggmux.c @@ -502,7 +502,7 @@ gst_ogg_mux_buffer_from_page (GstOggMux * mux, ogg_page * page, gboolean delta) GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); GST_LOG_OBJECT (mux, GST_GP_FORMAT - " created buffer %p from ogg page", ogg_page_granulepos (page)); + " created buffer %p from ogg page", ogg_page_granulepos (page), buffer); return buffer; } diff --git a/gst-libs/gst/riff/riff-media.c b/gst-libs/gst/riff/riff-media.c index a9cd0e7453..982900144e 100644 --- a/gst-libs/gst/riff/riff-media.c +++ b/gst-libs/gst/riff/riff-media.c @@ -900,13 +900,15 @@ gst_riff_create_audio_caps (guint16 codec_id, guint32 subformat_guid[4]; const guint8 *data; - if (strf_data == NULL || GST_BUFFER_SIZE (strf_data) != 22) { + /* should be at least 22 bytes */ + if (strf_data == NULL || GST_BUFFER_SIZE (strf_data) < 22) { GST_WARNING ("WAVE_FORMAT_EXTENSIBLE data size is %d (expected: 22)", (strf_data) ? GST_BUFFER_SIZE (strf_data) : -1); return NULL; } data = GST_BUFFER_DATA (strf_data); + valid_bits_per_sample = GST_READ_UINT16_LE (data); channel_mask = GST_READ_UINT32_LE (data + 2); subformat_guid[0] = GST_READ_UINT32_LE (data + 6); @@ -953,12 +955,18 @@ gst_riff_create_audio_caps (guint16 codec_id, } } else if (subformat_guid[0] == 0x00000003) { GST_DEBUG ("FIXME: handle IEEE float format"); + } else if (subformat_guid[0] == 0x00000092) { + GST_DEBUG ("FIXME: handle DOLBY AC3 SPDIF format"); } + } else if (subformat_guid[0] == 0x6ba47966 && + subformat_guid[1] == 0x41783f83 && + subformat_guid[2] == 0xf0006596 && subformat_guid[3] == 0xe59262bf) { + caps = gst_caps_new_simple ("application/x-ogg-avi", NULL); + } - if (caps == NULL) { - GST_WARNING ("Unknown WAVE_FORMAT_EXTENSIBLE audio format"); - return NULL; - } + if (caps == NULL) { + GST_WARNING ("Unknown WAVE_FORMAT_EXTENSIBLE audio format"); + return NULL; } break; } @@ -1127,6 +1135,8 @@ gst_riff_create_audio_template_caps (void) if (one) gst_caps_append (caps, one); } + one = gst_caps_new_simple ("application/x-ogg-avi", NULL); + gst_caps_append (caps, one); return caps; }