diff options
author | Michael Smith <msmith@xiph.org> | 2006-02-24 19:07:10 +0000 |
---|---|---|
committer | Michael Smith <msmith@xiph.org> | 2006-02-24 19:07:10 +0000 |
commit | 73ce8c5b9e9a9cb27c1307dbfee300939265650c (patch) | |
tree | 207f645f3849f86026894e7411d9335353a12e53 /ext/annodex/gstcmmlenc.c | |
parent | dd8f37faac9ea109550d630f4d7dcbf295cd5fc1 (diff) |
Add Annodex elements from Alessendro Decina: skeleton and CMML.
Original commit message from CVS:
* configure.ac:
* docs/plugins/gst-plugins-good-plugins-sections.txt:
* ext/Makefile.am:
* ext/annodex/Makefile.am:
* ext/annodex/gstannodex.c:
* ext/annodex/gstannodex.h:
* ext/annodex/gstcmmldec.c:
* ext/annodex/gstcmmldec.h:
* ext/annodex/gstcmmlenc.c:
* ext/annodex/gstcmmlenc.h:
* ext/annodex/gstcmmlparser.c:
* ext/annodex/gstcmmlparser.h:
* ext/annodex/gstcmmltag.c:
* ext/annodex/gstcmmltag.h:
* ext/annodex/gstcmmlutils.c:
* ext/annodex/gstcmmlutils.h:
* ext/annodex/gstskeldec.c:
* ext/annodex/gstskeldec.h:
* ext/annodex/gstskeltag.c:
* ext/annodex/gstskeltag.h:
* tests/check/Makefile.am:
* tests/check/elements/cmmldec.c:
* tests/check/elements/cmmlenc.c:
* tests/check/elements/skeldec.c:
Add Annodex elements from Alessendro Decina: skeleton and CMML.
Includes tests & docs, oh my! Passes Thomas's -good checklist
entirely. Wow.
Diffstat (limited to 'ext/annodex/gstcmmlenc.c')
-rw-r--r-- | ext/annodex/gstcmmlenc.c | 624 |
1 files changed, 624 insertions, 0 deletions
diff --git a/ext/annodex/gstcmmlenc.c b/ext/annodex/gstcmmlenc.c new file mode 100644 index 000000000..7bc98bc87 --- /dev/null +++ b/ext/annodex/gstcmmlenc.c @@ -0,0 +1,624 @@ +/* + * gstcmmlenc.c - GStreamer CMML encoder + * Copyright (C) 2005 Alessandro Decina + * + * Authors: + * Alessandro Decina <alessandro@nnva.org> + * + * 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. + */ + +/** + * SECTION:element-cmmlenc + * @see_also: cmmldec, oggmux + * + * <refsect2> + * <para> Cmmlenc encodes a CMML document into a CMML stream. <ulink + * url="http://www.annodex.net/TR/draft-pfeiffer-cmml-02.html">CMML</ulink> is + * an XML markup language for time-continuous data maintained by the <ulink + * url="http:/www.annodex.org/">Annodex Foundation</ulink>. + * </para> + * <title>Example pipeline</title> + * <programlisting> + * gst-launch -v filesrc location=annotations.cmml ! cmmlenc ! oggmux name=mux ! filesink location=annotated.ogg + * </programlisting> + * </refsect2> + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include "gstcmmlenc.h" +#include "gstannodex.h" + +GST_DEBUG_CATEGORY (cmmlenc); +#define GST_CAT_DEFAULT cmmlenc + +#define CMML_IDENT_HEADER_SIZE 29 + +enum +{ + ARG_0, + GST_CMML_ENC_GRANULERATE_N, + GST_CMML_ENC_GRANULERATE_D, + GST_CMML_ENC_GRANULESHIFT +}; + +enum +{ + LAST_SIGNAL +}; + +static GstElementDetails gst_cmml_enc_details = { + "cmmlenc: Encodes CMML streams", "Codec/Encoder", + "Encodes CMML streams", + "Alessandro Decina <alessandro@nnva.org>", +}; + +static GstStaticPadTemplate gst_cmml_enc_src_factory = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("text/x-cmml") + ); + +static GstStaticPadTemplate gst_cmml_enc_sink_factory = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("text/xml") + ); + +GST_BOILERPLATE (GstCmmlEnc, gst_cmml_enc, GstElement, GST_TYPE_ELEMENT); +static void gst_cmml_enc_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec); +static void gst_cmml_enc_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec); +static gboolean gst_cmml_enc_sink_event (GstPad * pad, GstEvent * event); +static GstStateChangeReturn gst_cmml_enc_change_state (GstElement * element, + GstStateChange transition); +static GstFlowReturn gst_cmml_enc_chain (GstPad * pad, GstBuffer * buffer); +static void gst_cmml_enc_parse_preamble (GstCmmlEnc * enc, + guchar * preamble, guchar * processing_instruction); +static void gst_cmml_enc_parse_end_tag (GstCmmlEnc * enc); +static void gst_cmml_enc_parse_tag_head (GstCmmlEnc * enc, + GstCmmlTagHead * head); +static void gst_cmml_enc_parse_tag_clip (GstCmmlEnc * enc, + GstCmmlTagClip * tag); +static GstFlowReturn gst_cmml_enc_new_buffer (GstCmmlEnc * enc, + guchar * data, gint size, GstBuffer ** buffer); +#if 0 +static void gst_cmml_enc_flush_clips (GstCmmlEnc * enc); +#endif +static GstFlowReturn gst_cmml_enc_push_clip (GstCmmlEnc * enc, + GstCmmlTagClip * clip, GstClockTime prev_clip_time); +static GstFlowReturn gst_cmml_enc_push (GstCmmlEnc * enc, GstBuffer * buffer); + +static void +gst_cmml_enc_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_cmml_enc_sink_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_cmml_enc_src_factory)); + gst_element_class_set_details (element_class, &gst_cmml_enc_details); +} + +static void +gst_cmml_enc_class_init (GstCmmlEncClass * enc_class) +{ + GObjectClass *klass = G_OBJECT_CLASS (enc_class); + + klass->get_property = gst_cmml_enc_get_property; + klass->set_property = gst_cmml_enc_set_property; + + g_object_class_install_property (klass, GST_CMML_ENC_GRANULERATE_N, + g_param_spec_int64 ("granule-rate-numerator", + "Granulerate numerator", + "Granulerate numerator", + 0, G_MAXINT64, 1, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (klass, GST_CMML_ENC_GRANULERATE_D, + g_param_spec_int64 ("granule-rate-denominator", + "Granulerate denominator", + "Granulerate denominator", + 0, G_MAXINT64, 1000, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (klass, GST_CMML_ENC_GRANULESHIFT, + g_param_spec_uchar ("granule-shift", + "Granuleshift", + "The number of lower bits to use for partitioning a granule position", + 0, G_MAXUINT8, 32, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + GST_ELEMENT_CLASS (klass)->change_state = gst_cmml_enc_change_state; +} + +static void +gst_cmml_enc_init (GstCmmlEnc * enc, GstCmmlEncClass * klass) +{ + enc->sinkpad = + gst_pad_new_from_static_template (&gst_cmml_enc_sink_factory, "sink"); + gst_pad_set_chain_function (enc->sinkpad, gst_cmml_enc_chain); + gst_pad_set_event_function (enc->sinkpad, gst_cmml_enc_sink_event); + gst_element_add_pad (GST_ELEMENT (enc), enc->sinkpad); + + enc->srcpad = + gst_pad_new_from_static_template (&gst_cmml_enc_src_factory, "src"); + gst_element_add_pad (GST_ELEMENT (enc), enc->srcpad); + + enc->major = 3; + enc->minor = 0; +} + +static void +gst_cmml_enc_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GstCmmlEnc *enc = GST_CMML_ENC (object); + + switch (property_id) { + case GST_CMML_ENC_GRANULERATE_N: + /* XXX: may need to flush clips */ + enc->granulerate_n = g_value_get_int64 (value); + break; + case GST_CMML_ENC_GRANULERATE_D: + enc->granulerate_d = g_value_get_int64 (value); + break; + case GST_CMML_ENC_GRANULESHIFT: + enc->granuleshift = g_value_get_uchar (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +gst_cmml_enc_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GstCmmlEnc *enc = GST_CMML_ENC (object); + + switch (property_id) { + case GST_CMML_ENC_GRANULERATE_N: + g_value_set_int64 (value, enc->granulerate_n); + break; + case GST_CMML_ENC_GRANULERATE_D: + g_value_set_int64 (value, enc->granulerate_d); + break; + case GST_CMML_ENC_GRANULESHIFT: + g_value_set_uchar (value, enc->granuleshift); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static GstStateChangeReturn +gst_cmml_enc_change_state (GstElement * element, GstStateChange transition) +{ + GstCmmlEnc *enc = GST_CMML_ENC (element); + GstStateChangeReturn res; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + enc->parser = gst_cmml_parser_new (GST_CMML_PARSER_ENCODE); + enc->parser->user_data = enc; + enc->parser->preamble_callback = + (GstCmmlParserPreambleCallback) gst_cmml_enc_parse_preamble; + enc->parser->head_callback = + (GstCmmlParserHeadCallback) gst_cmml_enc_parse_tag_head; + enc->parser->clip_callback = + (GstCmmlParserClipCallback) gst_cmml_enc_parse_tag_clip; + enc->parser->cmml_end_callback = + (GstCmmlParserCmmlEndCallback) gst_cmml_enc_parse_end_tag; + enc->tracks = gst_cmml_track_list_new (); + enc->sent_headers = FALSE; + enc->sent_eos = FALSE; + enc->flow_return = GST_FLOW_OK; + break; + default: + break; + } + + res = parent_class->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + { + gst_cmml_track_list_destroy (enc->tracks); + g_free (enc->preamble); + gst_cmml_parser_free (enc->parser); + break; + } + default: + break; + } + + return res; +} + +static gboolean +gst_cmml_enc_sink_event (GstPad * pad, GstEvent * event) +{ + GstCmmlEnc *enc = GST_CMML_ENC (GST_PAD_PARENT (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + { + if (!enc->sent_eos) + gst_cmml_enc_parse_end_tag (enc); + + break; + } + default: + break; + } + + return gst_pad_event_default (pad, event); +} + +static GstFlowReturn +gst_cmml_enc_new_buffer (GstCmmlEnc * enc, + guchar * data, gint size, GstBuffer ** buffer) +{ + GstFlowReturn res; + + res = gst_pad_alloc_buffer (enc->srcpad, GST_BUFFER_OFFSET_NONE, size, + NULL, buffer); + if (res == GST_FLOW_OK) { + if (data) + memcpy (GST_BUFFER_DATA (*buffer), data, size); + } else { + GST_ELEMENT_ERROR (enc, STREAM, ENCODE, + (NULL), ("alloc function returned error %s", gst_flow_get_name (res))); + } + + return res; +} + +static GstCaps * +gst_cmml_enc_set_header_on_caps (GstCaps * caps, + GstBuffer * ident, GstBuffer * preamble, GstBuffer * head) +{ + GValue array = { 0 }; + GValue value = { 0 }; + GstStructure *structure; + + caps = gst_caps_make_writable (caps); + structure = gst_caps_get_structure (caps, 0); + + g_value_init (&array, GST_TYPE_ARRAY); + g_value_init (&value, GST_TYPE_BUFFER); + + gst_value_set_buffer (&value, ident); + gst_value_array_append_value (&array, &value); + gst_value_set_buffer (&value, preamble); + gst_value_array_append_value (&array, &value); + gst_value_set_buffer (&value, head); + gst_value_array_append_value (&array, &value); + + GST_BUFFER_FLAG_SET (ident, GST_BUFFER_FLAG_IN_CAPS); + GST_BUFFER_FLAG_SET (preamble, GST_BUFFER_FLAG_IN_CAPS); + GST_BUFFER_FLAG_SET (head, GST_BUFFER_FLAG_IN_CAPS); + + gst_structure_set_value (structure, "streamheader", &array); + + g_value_unset (&value); + g_value_unset (&array); + + return caps; +} + +/* create a CMML ident header buffer + */ +static GstFlowReturn +gst_cmml_enc_new_ident_header (GstCmmlEnc * enc, GstBuffer ** buffer) +{ + guint8 ident_header[CMML_IDENT_HEADER_SIZE]; + guint8 *wptr = ident_header; + + memcpy (wptr, "CMML\0\0\0\0", 8); + wptr += 8; + GST_WRITE_UINT16_LE (wptr, enc->major); + wptr += 2; + GST_WRITE_UINT16_LE (wptr, enc->minor); + wptr += 2; + GST_WRITE_UINT64_LE (wptr, enc->granulerate_n); + wptr += 8; + GST_WRITE_UINT64_LE (wptr, enc->granulerate_d); + wptr += 8; + *wptr = enc->granuleshift; + + return gst_cmml_enc_new_buffer (enc, + (guchar *) & ident_header, CMML_IDENT_HEADER_SIZE, buffer); +} + +/* parse the CMML preamble */ +static void +gst_cmml_enc_parse_preamble (GstCmmlEnc * enc, + guchar * preamble, guchar * processing_instruction) +{ + GST_INFO_OBJECT (enc, "parsing preamble"); + + /* save the preamble: it will be pushed when the head tag is found */ + enc->preamble = (guchar *) g_strconcat ((gchar *) preamble, + (gchar *) processing_instruction, NULL); +} + +/* parse the CMML end tag */ +static void +gst_cmml_enc_parse_end_tag (GstCmmlEnc * enc) +{ + GstBuffer *buffer; + + GST_INFO_OBJECT (enc, "parsing end tag"); +#if 0 + if (!enc->streaming) + gst_cmml_enc_flush_clips (enc); + + if (GST_FLOW_IS_FATAL (enc->flow_return)) + goto done; +#endif + + /* push an empty buffer to signal EOS */ + enc->flow_return = gst_cmml_enc_new_buffer (enc, NULL, 0, &buffer); + if (enc->flow_return == GST_FLOW_OK) { + /* set granulepos 0 on EOS */ + GST_BUFFER_OFFSET_END (buffer) = 0; + enc->flow_return = gst_cmml_enc_push (enc, buffer); + enc->sent_eos = TRUE; + } + + return; +} + +/* encode the CMML head tag and push the CMML headers + */ +static void +gst_cmml_enc_parse_tag_head (GstCmmlEnc * enc, GstCmmlTagHead * head) +{ + GList *headers = NULL; + GList *walk; + guchar *head_string; + GstCaps *caps; + GstBuffer *ident_buf, *preamble_buf, *head_buf; + GstBuffer *buffer; + + if (enc->preamble == NULL) + goto flow_unexpected; + + GST_INFO_OBJECT (enc, "parsing head tag"); + + enc->flow_return = gst_cmml_enc_new_ident_header (enc, &ident_buf); + if (enc->flow_return != GST_FLOW_OK) + goto alloc_error; + headers = g_list_append (headers, ident_buf); + + enc->flow_return = gst_cmml_enc_new_buffer (enc, + enc->preamble, strlen ((gchar *) enc->preamble), &preamble_buf); + if (enc->flow_return != GST_FLOW_OK) + goto alloc_error; + headers = g_list_append (headers, preamble_buf); + + head_string = gst_cmml_parser_tag_head_to_string (enc->parser, head); + enc->flow_return = gst_cmml_enc_new_buffer (enc, + head_string, strlen ((gchar *) head_string), &head_buf); + g_free (head_string); + if (enc->flow_return != GST_FLOW_OK) + goto alloc_error; + headers = g_list_append (headers, head_buf); + + caps = gst_pad_get_caps (enc->srcpad); + /* this call unrefs caps and creates new caps */ + caps = gst_cmml_enc_set_header_on_caps (caps, + ident_buf, preamble_buf, head_buf); + + for (walk = headers; walk; walk = walk->next) { + buffer = GST_BUFFER (walk->data); + /* set granulepos 0 on headers */ + GST_BUFFER_OFFSET_END (buffer) = 0; + gst_buffer_set_caps (buffer, caps); + + enc->flow_return = gst_cmml_enc_push (enc, buffer); + if (GST_FLOW_IS_FATAL (enc->flow_return)) + goto push_error; + + } + + gst_caps_unref (caps); + g_list_free (headers); + + enc->sent_headers = TRUE; + return; + +flow_unexpected: + GST_ELEMENT_ERROR (enc, STREAM, ENCODE, + (NULL), ("got head tag before preamble")); + enc->flow_return = GST_FLOW_UNEXPECTED; + return; +push_error: + gst_caps_unref (caps); + /* fallthrough */ +alloc_error: + for (walk = headers; walk; walk = walk->next) + gst_buffer_unref (GST_BUFFER (walk->data)); + g_list_free (headers); + return; +} + +#if 0 +static void +gst_cmml_enc_flush_clips (GstCmmlEnc * enc) +{ + GList *clips, *walk; + + clips = gst_cmml_track_list_get_clips (enc->tracks); + for (walk = clips; walk; walk = g_list_next (walk)) { + enc->flow_return = gst_cmml_enc_push_clip (enc, + GST_CMML_TAG_CLIP (walk->data)); + if (!GST_FLOW_IS_FATAL (enc->flow_return)) + break; + } +} +#endif + +/* encode a CMML clip tag + * remove the start and end attributes (GstCmmlParser does this itself) and + * push the tag with the timestamp of its start attribute. If the tag has the + * end attribute, create a new empty clip and encode it. + */ +static void +gst_cmml_enc_parse_tag_clip (GstCmmlEnc * enc, GstCmmlTagClip * clip) +{ + GstCmmlTagClip *prev_clip; + GstClockTime prev_clip_time = GST_CLOCK_TIME_NONE; + + /* this can happen if there's a programming error (eg user forgets to set + * the start-time property) or if one of the gst_cmml_clock_time_from_* + * overflows in the GstCmmlParser code */ + if (clip->start_time == GST_CLOCK_TIME_NONE) { + GST_ELEMENT_ERROR (enc, STREAM, ENCODE, + (NULL), ("invalid start time for clip (%s)", clip->id)); + enc->flow_return = GST_FLOW_ERROR; + + return; + } + + prev_clip = gst_cmml_track_list_get_track_last_clip (enc->tracks, + (gchar *) clip->track); + if (prev_clip) { + prev_clip_time = prev_clip->start_time; + gst_cmml_track_list_del_clip (enc->tracks, prev_clip); + } + + /* add the current clip to the tracklist */ + gst_cmml_track_list_add_clip (enc->tracks, clip); + + enc->flow_return = gst_cmml_enc_push_clip (enc, clip, prev_clip_time); +} + +static GstFlowReturn +gst_cmml_enc_push_clip (GstCmmlEnc * enc, GstCmmlTagClip * clip, + GstClockTime prev_clip_time) +{ + GstFlowReturn res; + GstBuffer *buffer; + gchar *clip_string; + gint64 granulepos; + + if (prev_clip_time != GST_CLOCK_TIME_NONE && + prev_clip_time > clip->start_time) { + GST_ELEMENT_ERROR (enc, STREAM, ENCODE, + (NULL), ("previous clip start time > current clip (%s) start time", + clip->id)); + return GST_FLOW_ERROR; + } + + /* encode the clip */ + clip_string = + (gchar *) gst_cmml_parser_tag_clip_to_string (enc->parser, clip); + + res = gst_cmml_enc_new_buffer (enc, + (guchar *) clip_string, strlen (clip_string), &buffer); + g_free (clip_string); + if (res != GST_FLOW_OK) + goto done; + + GST_INFO_OBJECT (enc, "encoding clip" + "(start-time: %" GST_TIME_FORMAT " end-time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (clip->start_time), GST_TIME_ARGS (clip->end_time)); + + /* set the granulepos */ + granulepos = gst_cmml_clock_time_to_granule (prev_clip_time, clip->start_time, + enc->granulerate_n, enc->granulerate_d, enc->granuleshift); + GST_BUFFER_OFFSET_END (buffer) = granulepos; + GST_BUFFER_TIMESTAMP (buffer) = clip->start_time; + + res = gst_cmml_enc_push (enc, buffer); + if (GST_FLOW_IS_FATAL (res)) + goto done; + + if (clip->end_time != GST_CLOCK_TIME_NONE) { + /* create a new empty clip for the same cmml "track" starting at + * "end_time" + */ + GObject *end_clip = g_object_new (GST_TYPE_CMML_TAG_CLIP, + "start-time", clip->end_time, "track", clip->track, NULL); + + /* encode the empty end clip */ + gst_cmml_enc_push_clip (enc, GST_CMML_TAG_CLIP (end_clip), + clip->start_time); + g_object_unref (end_clip); + } +done: + return res; +} + +static GstFlowReturn +gst_cmml_enc_push (GstCmmlEnc * enc, GstBuffer * buffer) +{ + GstFlowReturn res; + + /* FIXME: hack to make oggmux flush every cmml tag in its own page */ + GST_BUFFER_DURATION (buffer) = G_MAXINT64; + + res = gst_pad_push (enc->srcpad, buffer); + if (GST_FLOW_IS_FATAL (res)) + GST_ELEMENT_ERROR (enc, STREAM, ENCODE, + (NULL), ("could not push the buffer: %s", gst_flow_get_name (res))); + + return res; +} + +static GstFlowReturn +gst_cmml_enc_chain (GstPad * pad, GstBuffer * buffer) +{ + GError *err = NULL; + GstCmmlEnc *enc = GST_CMML_ENC (GST_PAD_PARENT (pad)); + + /* the CMML handlers registered with enc->parser will override this when + * encoding/pushing the buffers downstream + */ + enc->flow_return = GST_FLOW_OK; + + if (!gst_cmml_parser_parse_chunk (enc->parser, + (gchar *) GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer), &err)) { + GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL), (err->message)); + g_error_free (err); + enc->flow_return = GST_FLOW_ERROR; + } + + gst_buffer_unref (buffer); + return enc->flow_return; +} + +gboolean +gst_cmml_enc_plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "cmmlenc", GST_RANK_NONE, + GST_TYPE_CMML_ENC)) + return FALSE; + + GST_DEBUG_CATEGORY_INIT (cmmlenc, "cmmlenc", 0, + "annodex cmml decoding element"); + + return TRUE; +} |