gstreamer/gst/matroska/ebml-read.c
Ronald S. Bultje 75ff7535dd gst/matroska/: Disgustingly evil hack for working around INTERRUPT events and their extremely annoying habit of being...
Original commit message from CVS:
* gst/matroska/ebml-read.c: (gst_ebml_read_class_init),
(gst_ebml_read_init), (gst_ebml_read_use_event),
(gst_ebml_read_element_id), (gst_ebml_peek_id),
(gst_ebml_read_seek), (gst_ebml_read_skip),
(gst_ebml_read_reserve), (gst_ebml_read_buffer),
(gst_ebml_read_master):
* gst/matroska/ebml-read.h:
* gst/matroska/matroska-demux.c:
(gst_matroska_demux_parse_contents),
(gst_matroska_demux_loop_stream), (gst_matroska_demux_audio_caps):
Disgustingly evil hack for working around INTERRUPT events and
their extremely annoying habit of being a pain in the ass. We
simply peek a cluster before reading any of it.
2004-12-03 21:08:17 +00:00

864 lines
19 KiB
C

/* 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"
GST_DEBUG_CATEGORY_STATIC (ebmlread_debug);
#define GST_CAT_DEFAULT ebmlread_debug
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);
GST_DEBUG_CATEGORY_INIT (ebmlread_debug, "ebmlread",
0, "EBML stream helper class");
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;
ebml->id_cache = 0;
}
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;
}
/*
* Event handler. Basic:
* - EOS: end-of-file, stop processing, forward EOS.
* - Interrupt: stop processing.
* - Discont: shouldn't be handled here but in the seek handler. Error.
* - Flush: ignore, since we check for flush flags manually. Don't forward.
* - Others: warn, ignore.
* Return value indicates whether to continue processing.
*/
static gboolean
gst_ebml_read_use_event (GstEbmlRead * ebml, GstEvent * event)
{
if (!event) {
GST_ELEMENT_ERROR (ebml, RESOURCE, READ, (NULL), (NULL));
return FALSE;
}
GST_LOG ("Received event of type %d", GST_EVENT_TYPE (event));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
gst_pad_event_default (ebml->sinkpad, event);
return FALSE;
case GST_EVENT_INTERRUPT:
gst_event_unref (event);
return FALSE;
case GST_EVENT_DISCONTINUOUS:
GST_WARNING_OBJECT (ebml, "Unexpected discont - might lose sync");
gst_event_unref (event);
return TRUE;
case GST_EVENT_FLUSH:
gst_event_unref (event);
return TRUE;
default:
GST_WARNING ("don't know how to handle event %d", GST_EVENT_TYPE (event));
gst_pad_event_default (ebml->sinkpad, event);
return FALSE;
}
/* happy */
g_assert_not_reached ();
return FALSE;
}
static gboolean
gst_ebml_read_handle_event (GstEbmlRead * ebml)
{
GstEvent *event = NULL;
guint32 remaining;
gst_bytestream_get_status (ebml->bs, &remaining, &event);
return gst_ebml_read_use_event (ebml, event);
}
/*
* 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 (ebml->id_cache) {
*id = ebml->id_cache;
if (level_up)
*level_up = 0;
return 0;
}
while (gst_bytestream_peek_bytes (ebml->bs, &data, 1) != 1) {
if (!gst_ebml_read_handle_event (ebml))
return -1;
}
total = data[0];
while (read <= 4 && !(total & len_mask)) {
read++;
len_mask >>= 1;
}
if (read > 4) {
guint64 pos = gst_bytestream_tell (ebml->bs);
GST_ELEMENT_ERROR (ebml, STREAM, DEMUX, (NULL),
("Invalid EBML ID size tag (0x%x) at position %llu (0x%llx)",
data[0], pos, pos));
return -1;
}
while (gst_bytestream_peek_bytes (ebml->bs, &data, read) != read) {
if (!gst_ebml_read_handle_event (ebml))
return -1;
}
while (n < read)
total = (total << 8) | data[n++];
*id = ebml->id_cache = total;
/* level */
if (level_up)
*level_up = gst_ebml_read_element_level_up (ebml);
gst_bytestream_flush_fast (ebml->bs, read);
return 0;
}
/*
* 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;
while (gst_bytestream_peek_bytes (ebml->bs, &data, 1) != 1) {
if (!gst_ebml_read_handle_event (ebml))
return -1;
}
total = data[0];
while (read <= 8 && !(total & len_mask)) {
read++;
len_mask >>= 1;
}
if (read > 8) {
guint64 pos = gst_bytestream_tell (ebml->bs);
GST_ELEMENT_ERROR (ebml, STREAM, DEMUX, (NULL),
("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++;
while (gst_bytestream_peek_bytes (ebml->bs, &data, read) != read) {
if (!gst_ebml_read_handle_event (ebml))
return -1;
}
while (n < read) {
if (data[n] == 0xff)
num_ffs++;
total = (total << 8) | data[n];
n++;
}
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) {
if (!gst_ebml_read_handle_event (ebml))
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;
g_assert (level_up);
if (gst_ebml_read_element_id (ebml, &id, level_up) < 0)
return 0;
return id;
}
/*
* Seek to a given offset.
*/
GstEvent *
gst_ebml_read_seek (GstEbmlRead * ebml, guint64 offset)
{
guint32 remaining;
GstEvent *event = NULL;
guchar *data;
/* first, flush remaining buffers */
gst_bytestream_get_status (ebml->bs, &remaining, &event);
if (event) {
GST_WARNING ("Unexpected event before seek");
if (!gst_ebml_read_use_event (ebml, event))
return NULL;
event = NULL;
}
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 (ebml, RESOURCE, SEEK, (NULL),
("Seek to position %llu (0x%llx) failed", offset, offset));
return NULL;
}
while (!event) {
/* 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)) {
GST_WARNING ("Unexpected data after seek - this means seek failed");
break;
}
/* get the discont event and return */
gst_bytestream_get_status (ebml->bs, &remaining, &event);
if (!event) {
GST_WARNING ("No discontinuity event after seek - seek failed");
break;
} else if (GST_EVENT_TYPE (event) != GST_EVENT_DISCONTINUOUS) {
if (!gst_ebml_read_use_event (ebml, event))
return NULL;
event = NULL;
}
}
/* at the end of a seek, we have no cached ID anymore */
ebml->id_cache = 0;
return event;
}
/*
* Skip the next element.
*/
gboolean
gst_ebml_read_skip (GstEbmlRead * ebml)
{
gint bytes;
guint32 id, remaining;
guint64 length;
GstEvent *event;
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);
/* do we have enough bytes left to skip? */
gst_bytestream_get_status (ebml->bs, &remaining, &event);
if (event) {
g_warning ("Unexpected event before skip");
if (!gst_ebml_read_use_event (ebml, event))
return FALSE;
}
if (remaining >= length) {
ebml->id_cache = 0;
gst_bytestream_flush_fast (ebml->bs, length);
return TRUE;
}
if (!(event = gst_ebml_read_seek (ebml,
gst_bytestream_tell (ebml->bs) + length))) {
return FALSE;
}
gst_event_unref (event);
ebml->id_cache = 0;
return TRUE;
}
/*
* Make sure there's one whole next chunk available
* for uninterrupted reading.
*/
gboolean
gst_ebml_read_reserve (GstEbmlRead * ebml)
{
guint64 length;
gint bytes;
guint8 *data;
guint32 id;
/* make sure the ID is cached */
if (gst_ebml_read_element_id (ebml, &id, NULL) < 0)
return FALSE;
/* now we can peek the length */
if ((bytes = gst_ebml_read_element_length (ebml, &length)) < 0)
return FALSE;
length += bytes;
/* now check if we have that amount available */
while (gst_bytestream_peek_bytes (ebml->bs, &data, length) != length) {
if (!gst_ebml_read_handle_event (ebml))
return FALSE;
}
/* yes, available */
return TRUE;
}
/*
* 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);
ebml->id_cache = 0;
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 (ebml, STREAM, DEMUX, (NULL),
("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;
}
*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, negative = 0, n = 0;
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 (ebml, STREAM, DEMUX, (NULL),
("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;
}
if (data[0] & 0x80) {
negative = 1;
data[0] &= ~0x80;
}
*num = 0;
while (n < size) {
*num = (*num << 8) | data[n++];
}
/* make signed */
if (negative) {
*num = *num - (1LL << ((8 * size) - 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 (ebml, STREAM, DEMUX, (NULL),
("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;
}
if (size == 10) {
GST_ELEMENT_ERROR (ebml, CORE, NOT_IMPLEMENTED, (NULL),
("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.
* Returns the seconds since the unix epoch.
*/
gboolean
gst_ebml_read_date (GstEbmlRead * ebml, guint32 * id, gint64 * date)
{
gint64 ebml_date;
gboolean res = gst_ebml_read_sint (ebml, id, &ebml_date);
*date = (ebml_date / GST_SECOND) + GST_EBML_DATE_OFFSET;
return res;
}
/*
* 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);
ebml->id_cache = 0;
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 (ebml, STREAM, WRONG_TYPE, (NULL), (NULL));
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;
}