summaryrefslogtreecommitdiffstats
path: root/gst/matroska/ebml-read.c
diff options
context:
space:
mode:
Diffstat (limited to 'gst/matroska/ebml-read.c')
-rw-r--r--gst/matroska/ebml-read.c702
1 files changed, 702 insertions, 0 deletions
diff --git a/gst/matroska/ebml-read.c b/gst/matroska/ebml-read.c
new file mode 100644
index 00000000..a6b9225d
--- /dev/null
+++ b/gst/matroska/ebml-read.c
@@ -0,0 +1,702 @@
+/* GStreamer EBML I/O
+ * (c) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
+ *
+ * ebml-read.c: read EBML data from 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 <string.h>
+
+#include "ebml-read.h"
+#include "ebml-ids.h"
+
+enum {
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+static void gst_ebml_read_class_init (GstEbmlReadClass *klass);
+static void gst_ebml_read_init (GstEbmlRead *ebml);
+static GstElementStateReturn
+ gst_ebml_read_change_state (GstElement *element);
+
+static GstElementClass *parent_class = NULL;
+
+GType
+gst_ebml_read_get_type (void)
+{
+ static GType gst_ebml_read_type = 0;
+
+ if (!gst_ebml_read_type) {
+ static const GTypeInfo gst_ebml_read_info = {
+ sizeof (GstEbmlReadClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gst_ebml_read_class_init,
+ NULL,
+ NULL,
+ sizeof (GstEbmlRead),
+ 0,
+ (GInstanceInitFunc) gst_ebml_read_init,
+ };
+
+ gst_ebml_read_type =
+ g_type_register_static (GST_TYPE_ELEMENT, "GstEbmlRead",
+ &gst_ebml_read_info, 0);
+ }
+
+ return gst_ebml_read_type;
+}
+
+static void
+gst_ebml_read_class_init (GstEbmlReadClass *klass)
+{
+ GstElementClass *gstelement_class = (GstElementClass *) klass;
+
+ parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
+
+ gstelement_class->change_state = gst_ebml_read_change_state;
+}
+
+static void
+gst_ebml_read_init (GstEbmlRead *ebml)
+{
+ ebml->sinkpad = NULL;
+ ebml->bs = NULL;
+ ebml->level = NULL;
+}
+
+static GstElementStateReturn
+gst_ebml_read_change_state (GstElement *element)
+{
+ GstEbmlRead *ebml = GST_EBML_READ (element);
+
+ switch (GST_STATE_TRANSITION (element)) {
+ case GST_STATE_READY_TO_PAUSED:
+ if (!ebml->sinkpad)
+ return GST_STATE_FAILURE;
+ ebml->bs = gst_bytestream_new (ebml->sinkpad);
+ break;
+ case GST_STATE_PAUSED_TO_READY:
+ gst_bytestream_destroy (ebml->bs);
+ while (ebml->level) {
+ GstEbmlLevel *level = ebml->level->data;
+
+ ebml->level = g_list_remove (ebml->level, level);
+ g_free (level);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (GST_ELEMENT_CLASS (parent_class)->change_state)
+ return GST_ELEMENT_CLASS (parent_class)->change_state (element);
+
+ return GST_STATE_SUCCESS;
+}
+
+/*
+ * Return: the amount of levels in the hierarchy that the
+ * current element lies higher than the previous one.
+ * The opposite isn't done - that's auto-done using master
+ * element reading.
+ */
+
+static guint
+gst_ebml_read_element_level_up (GstEbmlRead *ebml)
+{
+ guint num = 0;
+ guint64 pos = gst_bytestream_tell (ebml->bs);
+
+ while (ebml->level != NULL) {
+ GList *last = g_list_last (ebml->level);
+ GstEbmlLevel *level = last->data;
+
+ if (pos >= level->start + level->length) {
+ ebml->level = g_list_remove (ebml->level, level);
+ g_free (level);
+ num++;
+ } else
+ break;
+ }
+
+ return num;
+}
+
+/*
+ * Read: the element content data ID.
+ * Return: the number of bytes read or -1 on error.
+ */
+
+static gint
+gst_ebml_read_element_id (GstEbmlRead *ebml,
+ guint32 *id,
+ guint *level_up)
+{
+ guint8 *data;
+ gint len_mask = 0x80, read = 1, n = 1;
+ guint32 total;
+
+ if (gst_bytestream_peek_bytes (ebml->bs, &data, 1) != 1) {
+ /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/
+ return -1;
+ }
+ total = data[0];
+ while (read <= 4 && !(total & len_mask)) {
+ read++;
+ len_mask >>= 1;
+ }
+ if (read > 4) {
+ gst_element_error (GST_ELEMENT (ebml),
+ "Invalid EBML ID size tag (0x%x)", data[0]);
+ return -1;
+ }
+
+ if (gst_bytestream_peek_bytes (ebml->bs, &data, read) != read) {
+ /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/
+ return -1;
+ }
+ while (n < read)
+ total = (total << 8) | data[n++];
+
+ *id = total;
+
+ /* level */
+ if (level_up)
+ *level_up = gst_ebml_read_element_level_up (ebml);
+
+ return read;
+}
+
+/*
+ * Read: element content length.
+ * Return: the number of bytes read or -1 on error.
+ */
+
+static gint
+gst_ebml_read_element_length (GstEbmlRead *ebml,
+ guint64 *length)
+{
+ guint8 *data;
+ gint len_mask = 0x80, read = 1, n = 1, num_ffs = 0;
+ guint64 total;
+
+ if (gst_bytestream_peek_bytes (ebml->bs, &data, 1) != 1) {
+ /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/
+ return -1;
+ }
+ total = data[0];
+ while (read <= 8 && !(total & len_mask)) {
+ read++;
+ len_mask >>= 1;
+ }
+ if (read > 8) {
+ gst_element_error (GST_ELEMENT (ebml),
+ "Invalid EBML length size tag (0x%x)", data[0]);
+ 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");*/
+ return -1;
+ }
+ while (n < read) {
+ if (data[n] == 0xff)
+ num_ffs++;
+ total = (total << 8) | data[n];
+ n++;
+ }
+
+ if (!total) {
+ gst_element_error (GST_ELEMENT (ebml),
+ "Invalid length 0");
+ return -1;
+ }
+
+ if (read == num_ffs)
+ *length = G_MAXUINT64;
+ else
+ *length = total;
+
+ return read;
+}
+
+/*
+ * Read: the actual data.
+ * Return: the data, as a GstBuffer.
+ */
+
+static GstBuffer *
+gst_ebml_read_element_data (GstEbmlRead *ebml,
+ guint64 length)
+{
+ GstBuffer *buf = NULL;
+
+ if (gst_bytestream_peek (ebml->bs, &buf, length) != length) {
+ /*gst_element_error (GST_ELEMENT (ebml), "Read error");*/
+ if (buf)
+ gst_buffer_unref (buf);
+ return NULL;
+ }
+
+ gst_bytestream_flush_fast (ebml->bs, length);
+
+ return buf;
+}
+
+/*
+ * Return: the ID of the next element.
+ * Level_up contains the amount of levels that this
+ * next element lies higher than the previous one.
+ */
+
+guint32
+gst_ebml_peek_id (GstEbmlRead *ebml,
+ guint *level_up)
+{
+ guint32 id;
+ guint my_level_up;
+
+ g_return_val_if_fail (level_up != NULL, 0);
+
+ if (gst_ebml_read_element_id (ebml, &id, &my_level_up) < 0)
+ return 0;
+
+ if (level_up)
+ *level_up = my_level_up;
+
+ return id;
+}
+
+/*
+ * Seek to a given offset.
+ */
+
+void
+gst_ebml_read_seek (GstEbmlRead *ebml,
+ guint64 offset)
+{
+ gst_bytestream_seek (ebml->bs, offset, GST_SEEK_METHOD_SET);
+}
+
+/*
+ * Skip the next element.
+ */
+
+gboolean
+gst_ebml_read_skip (GstEbmlRead *ebml)
+{
+ gint bytes;
+ guint32 id;
+ guint64 length;
+
+ if ((bytes = gst_ebml_read_element_id (ebml, &id, NULL)) < 0)
+ return FALSE;
+ gst_bytestream_flush_fast (ebml->bs, bytes);
+
+ if ((bytes = gst_ebml_read_element_length (ebml, &length)) < 0)
+ return FALSE;
+ gst_bytestream_flush_fast (ebml->bs, bytes);
+
+ return gst_bytestream_flush (ebml->bs, length);
+}
+
+/*
+ * Read the next element as a GstBuffer (binary).
+ */
+
+gboolean
+gst_ebml_read_buffer (GstEbmlRead *ebml,
+ guint32 *id,
+ GstBuffer **buf)
+{
+ gint bytes;
+ guint64 length;
+
+ if ((bytes = gst_ebml_read_element_id (ebml, id, NULL)) < 0)
+ return FALSE;
+ gst_bytestream_flush_fast (ebml->bs, bytes);
+
+ if ((bytes = gst_ebml_read_element_length (ebml, &length)) < 0)
+ return FALSE;
+ gst_bytestream_flush_fast (ebml->bs, bytes);
+
+ return ((*buf = gst_ebml_read_element_data (ebml, length)) != NULL);
+}
+
+/*
+ * Read the next element as an unsigned int.
+ */
+
+gboolean
+gst_ebml_read_uint (GstEbmlRead *ebml,
+ guint32 *id,
+ guint64 *num)
+{
+ GstBuffer *buf;
+ guint8 *data;
+ guint size;
+
+ if (!gst_ebml_read_buffer (ebml, id, &buf))
+ return FALSE;
+
+ data = GST_BUFFER_DATA (buf);
+ size = GST_BUFFER_SIZE (buf);
+ if (size < 1 || size > 8) {
+ gst_element_error (GST_ELEMENT (ebml),
+ "Invalid integer element size %d", size);
+ gst_buffer_unref (buf);
+ return FALSE;
+ }
+ *num = 0;
+ while (size > 0) {
+ *num = (*num << 8) | data[GST_BUFFER_SIZE (buf) - size];
+ size--;
+ }
+
+ gst_buffer_unref (buf);
+
+ return TRUE;
+}
+
+/*
+ * Read the next element as a signed int.
+ */
+
+gboolean
+gst_ebml_read_sint (GstEbmlRead *ebml,
+ guint32 *id,
+ gint64 *num)
+{
+ GstBuffer *buf;
+ guint8 *data;
+ guint size;
+
+ if (!gst_ebml_read_buffer (ebml, id, &buf))
+ return FALSE;
+
+ data = GST_BUFFER_DATA (buf);
+ size = GST_BUFFER_SIZE (buf);
+ if (size < 1 || size > 8) {
+ gst_element_error (GST_ELEMENT (ebml),
+ "Invalid integer element size %d", size);
+ gst_buffer_unref (buf);
+ return FALSE;
+ }
+ *num = 0;
+ while (size > 0) {
+ *num = (*num << 8) | data[GST_BUFFER_SIZE (buf) - size];
+ size--;
+ }
+
+ /* make signed */
+ *num -= (1LL << ((8 * GST_BUFFER_SIZE (buf)) - 1));
+
+ gst_buffer_unref (buf);
+
+ return TRUE;
+}
+
+/*
+ * Read the next element as a float.
+ */
+
+gboolean
+gst_ebml_read_float (GstEbmlRead *ebml,
+ guint32 *id,
+ gdouble *num)
+{
+ GstBuffer *buf;
+ guint8 *data;
+ guint size;
+
+ if (!gst_ebml_read_buffer (ebml, id, &buf))
+ return FALSE;
+
+ data = GST_BUFFER_DATA (buf);
+ size = GST_BUFFER_SIZE (buf);
+
+ if (size != 4 && size != 8 && size != 10) {
+ gst_element_error (GST_ELEMENT (ebml),
+ "Invalid float element size %d", size);
+ gst_buffer_unref (buf);
+ return FALSE;
+ }
+
+ if (size == 10) {
+ gst_element_error (GST_ELEMENT (ebml),
+ "FIXME! 10-byte floats unimplemented");
+ gst_buffer_unref (buf);
+ return FALSE;
+ }
+
+ if (size == 4) {
+ gfloat f;
+
+#if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ f = * (gfloat *) data;
+#else
+ while (size > 0) {
+ ((guint8 *) &f)[size - 1] = data[4 - size];
+ size--;
+ }
+#endif
+
+ *num = f;
+ } else {
+ gdouble d;
+
+#if (G_BYTE_ORDER == G_BIG_ENDIAN)
+ d = * (gdouble *) data;
+#else
+ while (size > 0) {
+ ((guint8 *) &d)[size - 1] = data[8 - size];
+ size--;
+ }
+#endif
+
+ *num = d;
+ }
+
+ gst_buffer_unref (buf);
+
+ return TRUE;
+}
+
+/*
+ * Read the next element as an ASCII string.
+ */
+
+gboolean
+gst_ebml_read_ascii (GstEbmlRead *ebml,
+ guint32 *id,
+ gchar **str)
+{
+ GstBuffer *buf;
+
+ if (!gst_ebml_read_buffer (ebml, id, &buf))
+ return FALSE;
+
+ *str = g_malloc (GST_BUFFER_SIZE (buf) + 1);
+ memcpy (*str, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
+ (*str)[GST_BUFFER_SIZE (buf)] = '\0';
+
+ gst_buffer_unref (buf);
+
+ return TRUE;
+}
+
+/*
+ * Read the next element as a UTF-8 string.
+ */
+
+gboolean
+gst_ebml_read_utf8 (GstEbmlRead *ebml,
+ guint32 *id,
+ gchar **str)
+{
+ return gst_ebml_read_ascii (ebml, id, str);
+}
+
+/*
+ * Read the next element as a date (nanoseconds since 1/1/2000).
+ */
+
+gboolean
+gst_ebml_read_date (GstEbmlRead *ebml,
+ guint32 *id,
+ gint64 *date)
+{
+ return gst_ebml_read_sint (ebml, id, date);
+}
+
+/*
+ * Read the next element, but only the header. The contents
+ * are supposed to be sub-elements which can be read separately.
+ */
+
+gboolean
+gst_ebml_read_master (GstEbmlRead *ebml,
+ guint32 *id)
+{
+ gint bytes;
+ guint64 length;
+ GstEbmlLevel *level;
+
+ if ((bytes = gst_ebml_read_element_id (ebml, id, NULL)) < 0)
+ return FALSE;
+ gst_bytestream_flush_fast (ebml->bs, bytes);
+
+ if ((bytes = gst_ebml_read_element_length (ebml, &length)) < 0)
+ return FALSE;
+ gst_bytestream_flush_fast (ebml->bs, bytes);
+
+ /* remember level */
+ level = g_new (GstEbmlLevel, 1);
+ level->start = gst_bytestream_tell (ebml->bs);
+ level->length = length;
+ ebml->level = g_list_append (ebml->level, level);
+
+ return TRUE;
+}
+
+/*
+ * Read the next element as binary data.
+ */
+
+gboolean
+gst_ebml_read_binary (GstEbmlRead *ebml,
+ guint32 *id,
+ guint8 **binary,
+ guint64 *length)
+{
+ GstBuffer *buf;
+
+ if (!gst_ebml_read_buffer (ebml, id, &buf))
+ return FALSE;
+
+ *length = GST_BUFFER_SIZE (buf);
+ *binary = g_memdup (GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
+
+ gst_buffer_unref (buf);
+
+ return TRUE;
+}
+
+/*
+ * Read an EBML header.
+ */
+
+gboolean
+gst_ebml_read_header (GstEbmlRead *ebml,
+ gchar **doctype,
+ guint *version)
+{
+ /* this function is the first to be called */
+ guint32 id;
+ guint level_up;
+
+ /* default init */
+ if (doctype)
+ *doctype = NULL;
+ if (version)
+ *version = 1;
+
+ if (!(id = gst_ebml_peek_id (ebml, &level_up)))
+ return FALSE;
+ if (level_up != 0 || id != GST_EBML_ID_HEADER) {
+ gst_element_error (GST_ELEMENT (ebml), "Not a EBML file");
+ return FALSE;
+ }
+ if (!gst_ebml_read_master (ebml, &id))
+ return FALSE;
+ g_assert (id == GST_EBML_ID_HEADER);
+
+ while (TRUE) {
+ if (!(id = gst_ebml_peek_id (ebml, &level_up)))
+ return FALSE;
+
+ /* end-of-header */
+ if (level_up)
+ break;
+
+ switch (id) {
+ /* is our read version uptodate? */
+ case GST_EBML_ID_EBMLREADVERSION: {
+ guint64 num;
+
+ if (!gst_ebml_read_uint (ebml, &id, &num))
+ return FALSE;
+ g_assert (id == GST_EBML_ID_EBMLREADVERSION);
+ if (num != GST_EBML_VERSION)
+ return FALSE;
+ break;
+ }
+
+ /* we only handle 8 byte lengths at max */
+ case GST_EBML_ID_EBMLMAXSIZELENGTH: {
+ guint64 num;
+
+ if (!gst_ebml_read_uint (ebml, &id, &num))
+ return FALSE;
+ g_assert (id == GST_EBML_ID_EBMLMAXSIZELENGTH);
+ if (num != sizeof (guint64))
+ return FALSE;
+ break;
+ }
+
+ /* we handle 4 byte IDs at max */
+ case GST_EBML_ID_EBMLMAXIDLENGTH: {
+ guint64 num;
+
+ if (!gst_ebml_read_uint (ebml, &id, &num))
+ return FALSE;
+ g_assert (id == GST_EBML_ID_EBMLMAXIDLENGTH);
+ if (num != sizeof (guint32))
+ return FALSE;
+ break;
+ }
+
+ case GST_EBML_ID_DOCTYPE: {
+ gchar *text;
+
+ if (!gst_ebml_read_ascii (ebml, &id, &text))
+ return FALSE;
+ g_assert (id == GST_EBML_ID_DOCTYPE);
+ if (doctype) {
+ if (doctype)
+ g_free (*doctype);
+ *doctype = text;
+ } else
+ g_free (text);
+ break;
+ }
+
+ case GST_EBML_ID_DOCTYPEREADVERSION: {
+ guint64 num;
+
+ if (!gst_ebml_read_uint (ebml, &id, &num))
+ return FALSE;
+ g_assert (id == GST_EBML_ID_DOCTYPEREADVERSION);
+ if (version)
+ *version = num;
+ break;
+ }
+
+ default:
+ GST_WARNING ("Unknown data type 0x%x in EBML header (ignored)", id);
+ /* pass-through */
+
+ /* we ignore these two, as they don't tell us anything we care about */
+ case GST_EBML_ID_VOID:
+ case GST_EBML_ID_EBMLVERSION:
+ case GST_EBML_ID_DOCTYPEVERSION:
+ if (!gst_ebml_read_skip (ebml))
+ return FALSE;
+ break;
+ }
+ }
+
+ return TRUE;
+}