diff options
Diffstat (limited to 'gst/matroska/ebml-write.c')
-rw-r--r-- | gst/matroska/ebml-write.c | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/gst/matroska/ebml-write.c b/gst/matroska/ebml-write.c new file mode 100644 index 00000000..9c768953 --- /dev/null +++ b/gst/matroska/ebml-write.c @@ -0,0 +1,580 @@ +/* GStreamer EBML I/O + * (c) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net> + * + * ebml-write.c: write EBML data to 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-write.h" +#include "ebml-ids.h" + +enum { + /* FILL ME */ + LAST_SIGNAL +}; + +static void gst_ebml_write_class_init (GstEbmlWriteClass *klass); +static void gst_ebml_write_init (GstEbmlWrite *ebml); +static GstElementStateReturn + gst_ebml_write_change_state (GstElement *element); + +static GstElementClass *parent_class = NULL; + +GType +gst_ebml_write_get_type (void) +{ + static GType gst_ebml_write_type = 0; + + if (!gst_ebml_write_type) { + static const GTypeInfo gst_ebml_write_info = { + sizeof (GstEbmlWriteClass), + NULL, + NULL, + (GClassInitFunc) gst_ebml_write_class_init, + NULL, + NULL, + sizeof (GstEbmlWrite), + 0, + (GInstanceInitFunc) gst_ebml_write_init, + }; + + gst_ebml_write_type = + g_type_register_static (GST_TYPE_ELEMENT, "GstEbmlWrite", + &gst_ebml_write_info, 0); + } + + return gst_ebml_write_type; +} + +static void +gst_ebml_write_class_init (GstEbmlWriteClass *klass) +{ + GstElementClass *gstelement_class = (GstElementClass *) klass; + + parent_class = g_type_class_ref (GST_TYPE_ELEMENT); + + gstelement_class->change_state = gst_ebml_write_change_state; +} + +static void +gst_ebml_write_init (GstEbmlWrite *ebml) +{ + ebml->srcpad = NULL; + ebml->pos = 0; + + ebml->cache = NULL; +} + +static GstElementStateReturn +gst_ebml_write_change_state (GstElement *element) +{ + GstEbmlWrite *ebml = GST_EBML_WRITE (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_PAUSED_TO_READY: + ebml->pos = 0; + break; + default: + break; + } + + if (GST_ELEMENT_CLASS (parent_class)->change_state) + return GST_ELEMENT_CLASS (parent_class)->change_state (element); + + return GST_STATE_SUCCESS; +} + +/* + * Caching. + * + * The idea is that you use this for writing a lot + * of small elements. This will just "queue" all of + * them and they'll be pushed to the next element all + * at once. This saves memory and time for buffer + * allocation and init, and it looks better. + */ + +void +gst_ebml_write_set_cache (GstEbmlWrite *ebml, + guint size) +{ + return; + g_return_if_fail (ebml->cache == NULL); + + ebml->cache = gst_buffer_new_and_alloc (size); + GST_BUFFER_SIZE (ebml->cache) = 0; + GST_BUFFER_OFFSET (ebml->cache) = ebml->pos; + ebml->handled = 0; +} + +void +gst_ebml_write_flush_cache (GstEbmlWrite *ebml) +{ + if (!ebml->cache) + return; + + /* this is very important. It may fail, in which case the client + * programmer didn't use the cache somewhere. That's fatal. */ + g_assert (ebml->handled == GST_BUFFER_SIZE (ebml->cache)); + g_assert (GST_BUFFER_SIZE (ebml->cache) + + GST_BUFFER_OFFSET (ebml->cache) == ebml->pos); + + gst_pad_push (ebml->srcpad, GST_DATA (ebml->cache)); + ebml->cache = NULL; + ebml->handled = 0; +} + +/* + * One-element buffer, in case of no cache. If there is + * a cache, use that instead. + */ + +static GstBuffer * +gst_ebml_write_element_new (GstEbmlWrite *ebml, + guint size) +{ + /* Create new buffer of size + ID + length */ + GstBuffer *buf; + + /* length, ID */ + size += 12; + + /* prefer cache */ + if (ebml->cache) { + if (GST_BUFFER_MAXSIZE (ebml->cache) - + GST_BUFFER_SIZE (ebml->cache) < size) { + GST_LOG ("Cache available, but too small. Clearing..."); + gst_ebml_write_flush_cache (ebml); + } else { + return ebml->cache; + } + } + + /* else, use a one-element buffer. This is slower */ + buf = gst_buffer_new_and_alloc (size); + GST_BUFFER_SIZE (buf) = 0; + + return buf; +} + +/* + * Write element ID into a buffer. + */ + +static void +gst_ebml_write_element_id (GstBuffer *buf, + guint32 id) +{ + guint8 *data = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf); + guint bytes = 4, mask = 0x10; + + /* get ID length */ + while (!(id & (mask << ((bytes - 1) * 8))) && bytes > 0) { + mask <<= 1; + bytes--; + } + + /* if invalid ID, use dummy */ + if (bytes == 0) { + GST_WARNING ("Invalid ID, voiding"); + bytes = 1; + id = GST_EBML_ID_VOID; + } + + /* write out, BE */ + GST_BUFFER_SIZE (buf) += bytes; + while (bytes--) { + data[bytes] = id & 0xff; + id >>= 8; + } +} + +/* + * Write element length into a buffer. + */ + +static void +gst_ebml_write_element_size (GstBuffer *buf, + guint64 size) +{ + guint8 *data = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf); + guint bytes = 1, mask = 0x80; + + /* how many bytes? */ + while ((size >> ((bytes - 1) * 8)) >= mask && bytes <= 8) { + mask >>= 1; + bytes++; + } + + /* if invalid size, use max. */ + if (bytes > 8) { + GST_WARNING ("Invalid size, maximizing"); + mask = 0x01; + bytes = 8; + /* Now here's a real FIXME: we cannot read those yet! */ + size = 0x00ffffffffffffffLLU; + } + + /* write out, BE, with length size marker */ + GST_BUFFER_SIZE (buf) += bytes; + while (bytes-- > 0) { + data[bytes] = size & 0xff; + size >>= 8; + if (!bytes) + *data |= mask; + } +} + +/* + * Write element data into a buffer. + */ + +static void +gst_ebml_write_element_data (GstBuffer *buf, + guint8 *write, + guint64 length) +{ + guint8 *data = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf); + + memcpy (data, write, length); + GST_BUFFER_SIZE (buf) += length; +} + +/* + * Write out buffer by moving it to the next element. + */ + +static void +gst_ebml_write_element_push (GstEbmlWrite *ebml, + GstBuffer *buf) +{ + guint data_size = GST_BUFFER_SIZE (buf) - ebml->handled; + + ebml->pos += data_size; + if (buf == ebml->cache) { + ebml->handled += data_size; + } + + /* if there's no cache, then don't push it! */ + if (ebml->cache) + g_assert (buf == ebml->cache); + else + gst_pad_push (ebml->srcpad, GST_DATA (buf)); +} + +/* + * Seek. + */ + +void +gst_ebml_write_seek (GstEbmlWrite *ebml, + guint64 pos) +{ + GstEvent *seek; + + /* Cache seeking. A bit dangerous, we assume the client writer + * knows what he's doing... */ + if (ebml->cache) { + /* within bounds? */ + if (pos >= GST_BUFFER_OFFSET (ebml->cache) && + pos < GST_BUFFER_OFFSET (ebml->cache) + GST_BUFFER_MAXSIZE (ebml->cache)) { + GST_BUFFER_SIZE (ebml->cache) = pos - GST_BUFFER_OFFSET (ebml->cache); + if (ebml->pos > pos) + ebml->handled -= ebml->pos - pos; + else + ebml->handled += pos - ebml->pos; + ebml->pos = pos; + } else { + GST_LOG ("Seek outside cache range. Clearing..."); + gst_ebml_write_flush_cache (ebml); + } + } + + seek = gst_event_new_seek (GST_FORMAT_BYTES | + GST_SEEK_METHOD_SET, + pos); + gst_pad_push (ebml->srcpad, GST_DATA (seek)); + ebml->pos = pos; +} + +/* + * Get no. bytes needed to write a uint. + */ + +static guint +gst_ebml_write_get_uint_size (guint64 num) +{ + guint size = 1; + + /* get size */ + while (num >= (1LLU << (size * 8)) && size < 8) { + size++; + } + + return size; +} + + +/* + * Write an uint into a buffer. + */ + +static void +gst_ebml_write_set_uint (GstBuffer *buf, + guint64 num, + guint size) +{ + guint8 *data; + + data = GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf); + GST_BUFFER_SIZE (buf) += size; + while (size-- > 0) { + data[size] = num & 0xff; + num >>= 8; + } +} + +/* + * Data type wrappers. + */ + +void +gst_ebml_write_uint (GstEbmlWrite *ebml, + guint32 id, + guint64 num) +{ + GstBuffer *buf = gst_ebml_write_element_new (ebml, sizeof (num)); + guint size = gst_ebml_write_get_uint_size (num); + + /* write */ + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, size); + gst_ebml_write_set_uint (buf, num, size); + gst_ebml_write_element_push (ebml, buf); +} + +void +gst_ebml_write_sint (GstEbmlWrite *ebml, + guint32 id, + gint64 num) +{ + GstBuffer *buf = gst_ebml_write_element_new (ebml, sizeof (num)); + /* if the signed number is on the edge of a extra-byte, + * then we'll fall over when detecting it. Example: if I + * have a number (-)0x8000 (G_MINSHORT), then my abs()<<1 + * will be 0x10000; this is G_MAXUSHORT+1! So: if (<0) -1. */ + guint64 unum = (num < 0 ? (-num - 1) << 1 : num << 1); + guint size = gst_ebml_write_get_uint_size (unum); + + /* make unsigned */ + unum = (num < 0 ? -num : num) + (1LLU << ((8 * size) - 1)); + + /* write */ + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, size); + gst_ebml_write_set_uint (buf, unum, size); + gst_ebml_write_element_push (ebml, buf); +} + +void +gst_ebml_write_float (GstEbmlWrite *ebml, + guint32 id, + gdouble num) +{ + gint n; + GstBuffer *buf = gst_ebml_write_element_new (ebml, sizeof (num)); + + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, 8); +#if (G_BYTE_ORDER == G_LITTLE_ENDIAN) + for (n = 0; n < 8; n++) + GST_BUFFER_DATA (buf)[GST_BUFFER_SIZE (buf)] = ((guint8 *) &num)[7-n]; + GST_BUFFER_SIZE (buf) += 8; +#else + gst_ebml_write_element_data (buf, (guint8 *) &num, 8); +#endif + gst_ebml_write_element_push (ebml, buf); +} + +void +gst_ebml_write_ascii (GstEbmlWrite *ebml, + guint32 id, + const gchar *str) +{ + gint len = strlen (str) + 1; /* add trailing '\0' */ + GstBuffer *buf = gst_ebml_write_element_new (ebml, len); + + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, len); + gst_ebml_write_element_data (buf, (guint8 *) str, len); + gst_ebml_write_element_push (ebml, buf); +} + +void +gst_ebml_write_utf8 (GstEbmlWrite *ebml, + guint32 id, + const gchar *str) +{ + gst_ebml_write_ascii (ebml, id, str); +} + +void +gst_ebml_write_date (GstEbmlWrite *ebml, + guint32 id, + gint64 date) +{ + gst_ebml_write_sint (ebml, id, date); +} + +/* + * Master writing is annoying. We use a size marker of + * the max. allowed length, so that we can later fill it + * in validly. + */ + +guint64 +gst_ebml_write_master_start (GstEbmlWrite *ebml, + guint32 id) +{ + guint64 pos = ebml->pos, t; + GstBuffer *buf = gst_ebml_write_element_new (ebml, 0); + + t = GST_BUFFER_SIZE (buf); + gst_ebml_write_element_id (buf, id); + pos += GST_BUFFER_SIZE (buf) - t; + gst_ebml_write_element_size (buf, -1); + gst_ebml_write_element_push (ebml, buf); + + return pos; +} + +void +gst_ebml_write_master_finish (GstEbmlWrite *ebml, + guint64 startpos) +{ + guint64 pos = ebml->pos; + GstBuffer *buf; + + gst_ebml_write_seek (ebml, startpos); + buf = gst_ebml_write_element_new (ebml, 0); + startpos = GUINT64_TO_BE ((1LLU << 56) | (pos - startpos - 8)); + memcpy (GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf), + (guint8 *) &startpos, 8); + GST_BUFFER_SIZE (buf) += 8; + gst_ebml_write_element_push (ebml, buf); + gst_ebml_write_seek (ebml, pos); +} + +void +gst_ebml_write_binary (GstEbmlWrite *ebml, + guint32 id, + guint8 *binary, + guint64 length) +{ + GstBuffer *buf = gst_ebml_write_element_new (ebml, length); + + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, length); + gst_ebml_write_element_data (buf, binary, length); + gst_ebml_write_element_push (ebml, buf); +} + +/* + * For things like video frames and audio samples, + * you want to use this function, as it doesn't have + * the overhead of memcpy() that other functions + * such as write_binary() do have. + */ + +void +gst_ebml_write_buffer_header (GstEbmlWrite *ebml, + guint32 id, + guint64 length) +{ + GstBuffer *buf = gst_ebml_write_element_new (ebml, 0); + + gst_ebml_write_element_id (buf, id); + gst_ebml_write_element_size (buf, length); + gst_ebml_write_element_push (ebml, buf); +} + +void +gst_ebml_write_buffer (GstEbmlWrite *ebml, + GstBuffer *data) +{ + gst_ebml_write_element_push (ebml, data); +} + +/* + * When replacing a uint, we assume that it is *always* + * 8-byte, since that's the safest guess we can do. This + * is just for simplicity. + * + * FIXME: this function needs to be replaced with something + * proper. This is a crude hack. + */ + +void +gst_ebml_replace_uint (GstEbmlWrite *ebml, + guint64 pos, + guint64 num) +{ + guint64 oldpos = ebml->pos; + GstBuffer *buf = gst_buffer_new_and_alloc (8); + + gst_ebml_write_seek (ebml, pos); + GST_BUFFER_SIZE (buf) = 0; + gst_ebml_write_set_uint (buf, num, 8); + gst_ebml_write_element_push (ebml, buf); + gst_ebml_write_seek (ebml, oldpos); +} + +/* + * Write EBML header. + */ + +void +gst_ebml_write_header (GstEbmlWrite *ebml, + gchar *doctype, + guint version) +{ + guint64 pos; + + /* write the basic EBML header */ + gst_ebml_write_set_cache (ebml, 0x40); + pos = gst_ebml_write_master_start (ebml, GST_EBML_ID_HEADER); +#if (GST_EBML_VERSION != 1) + gst_ebml_write_uint (ebml, GST_EBML_ID_EBMLVERSION, GST_EBML_VERSION); + gst_ebml_write_uint (ebml, GST_EBML_ID_EBMLREADVERSION, GST_EBML_VERSION); +#endif +#if 0 + /* we don't write these until they're "non-default" (never!) */ + gst_ebml_write_uint (ebml, GST_EBML_ID_EBMLMAXIDLENGTH, sizeof (guint32)); + gst_ebml_write_uint (ebml, GST_EBML_ID_EBMLMAXSIZELENGTH, sizeof (guint64)); +#endif + gst_ebml_write_ascii (ebml, GST_EBML_ID_DOCTYPE, doctype); + gst_ebml_write_uint (ebml, GST_EBML_ID_DOCTYPEVERSION, version); + gst_ebml_write_uint (ebml, GST_EBML_ID_DOCTYPEREADVERSION, version); + gst_ebml_write_master_finish (ebml, pos); + gst_ebml_write_flush_cache (ebml); +} |