/* GStreamer EBML I/O * (c) 2003 Ronald Bultje * (c) 2005 Michal Benes * * 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 #include "ebml-write.h" #include "ebml-ids.h" GST_DEBUG_CATEGORY_STATIC (gst_ebml_write_debug); #define GST_CAT_DEFAULT gst_ebml_write_debug #define _do_init \ GST_DEBUG_CATEGORY_INIT (gst_ebml_write_debug, "ebmlwrite", 0, "Write EBML structured data") #define parent_class gst_ebml_write_parent_class G_DEFINE_TYPE_WITH_CODE (GstEbmlWrite, gst_ebml_write, GST_TYPE_OBJECT, _do_init); static void gst_ebml_write_finalize (GObject * object); static void gst_ebml_write_class_init (GstEbmlWriteClass * klass) { GObjectClass *object = G_OBJECT_CLASS (klass); object->finalize = gst_ebml_write_finalize; } static void gst_ebml_write_init (GstEbmlWrite * ebml) { ebml->srcpad = NULL; ebml->pos = 0; ebml->last_pos = G_MAXUINT64; /* force segment event */ ebml->cache = NULL; ebml->streamheader = NULL; ebml->streamheader_pos = 0; ebml->writing_streamheader = FALSE; ebml->caps = NULL; } static void gst_ebml_write_finalize (GObject * object) { GstEbmlWrite *ebml = GST_EBML_WRITE (object); gst_object_unref (ebml->srcpad); if (ebml->cache) { gst_byte_writer_free (ebml->cache); ebml->cache = NULL; } if (ebml->streamheader) { gst_byte_writer_free (ebml->streamheader); ebml->streamheader = NULL; } if (ebml->caps) { gst_caps_unref (ebml->caps); ebml->caps = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } /** * gst_ebml_write_new: * @srcpad: Source pad to which the output will be pushed. * * Creates a new #GstEbmlWrite. * * Returns: a new #GstEbmlWrite */ GstEbmlWrite * gst_ebml_write_new (GstPad * srcpad) { GstEbmlWrite *ebml = GST_EBML_WRITE (g_object_new (GST_TYPE_EBML_WRITE, NULL)); ebml->srcpad = gst_object_ref (srcpad); ebml->timestamp = GST_CLOCK_TIME_NONE; gst_ebml_write_reset (ebml); return ebml; } /** * gst_ebml_write_reset: * @ebml: a #GstEbmlWrite. * * Reset internal state of #GstEbmlWrite. */ void gst_ebml_write_reset (GstEbmlWrite * ebml) { ebml->pos = 0; ebml->last_pos = G_MAXUINT64; /* force segment event */ if (ebml->cache) { gst_byte_writer_free (ebml->cache); ebml->cache = NULL; } if (ebml->caps) { gst_caps_unref (ebml->caps); ebml->caps = NULL; } ebml->last_write_result = GST_FLOW_OK; ebml->timestamp = GST_CLOCK_TIME_NONE; } /** * gst_ebml_last_write_result: * @ebml: a #GstEbmlWrite. * * Returns: GST_FLOW_OK if there was not write error since the last call of * gst_ebml_last_write_result or code of the error. */ GstFlowReturn gst_ebml_last_write_result (GstEbmlWrite * ebml) { GstFlowReturn res = ebml->last_write_result; ebml->last_write_result = GST_FLOW_OK; return res; } void gst_ebml_start_streamheader (GstEbmlWrite * ebml) { g_return_if_fail (ebml->streamheader == NULL); GST_DEBUG ("Starting streamheader at %" G_GUINT64_FORMAT, ebml->pos); ebml->streamheader = gst_byte_writer_new_with_size (1000, FALSE); ebml->streamheader_pos = ebml->pos; ebml->writing_streamheader = TRUE; } GstBuffer * gst_ebml_stop_streamheader (GstEbmlWrite * ebml) { GstBuffer *buffer; if (!ebml->streamheader) return NULL; buffer = gst_byte_writer_free_and_get_buffer (ebml->streamheader); ebml->streamheader = NULL; GST_DEBUG ("Streamheader was size %d", gst_buffer_get_size (buffer)); ebml->writing_streamheader = FALSE; return buffer; } /** * gst_ebml_write_set_cache: * @ebml: a #GstEbmlWrite. * @size: size of the cache. * Create a cache. * * 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) { g_return_if_fail (ebml->cache == NULL); GST_DEBUG ("Starting cache at %" G_GUINT64_FORMAT, ebml->pos); ebml->cache = gst_byte_writer_new_with_size (size, FALSE); ebml->cache_pos = ebml->pos; } static gboolean gst_ebml_writer_send_segment_event (GstEbmlWrite * ebml, guint64 new_pos) { GstSegment segment; gboolean res; GST_INFO ("seeking to %" G_GUINT64_FORMAT, new_pos); gst_segment_init (&segment, GST_FORMAT_BYTES); segment.start = new_pos; segment.stop = -1; segment.position = 0; res = gst_pad_push_event (ebml->srcpad, gst_event_new_segment (&segment)); if (!res) GST_WARNING ("seek to %" G_GUINT64_FORMAT "failed", new_pos); return res; } /** * gst_ebml_write_flush_cache: * @ebml: a #GstEbmlWrite. * @timestamp: timestamp of the buffer. * * Flush the cache. */ void gst_ebml_write_flush_cache (GstEbmlWrite * ebml, gboolean is_keyframe, GstClockTime timestamp) { GstBuffer *buffer; if (!ebml->cache) return; buffer = gst_byte_writer_free_and_get_buffer (ebml->cache); ebml->cache = NULL; GST_DEBUG ("Flushing cache of size %d", gst_buffer_get_size (buffer)); GST_BUFFER_TIMESTAMP (buffer) = timestamp; GST_BUFFER_OFFSET (buffer) = ebml->pos - gst_buffer_get_size (buffer); GST_BUFFER_OFFSET_END (buffer) = ebml->pos; if (ebml->last_write_result == GST_FLOW_OK) { if (GST_BUFFER_OFFSET (buffer) != ebml->last_pos) { gst_ebml_writer_send_segment_event (ebml, GST_BUFFER_OFFSET (buffer)); GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); } if (ebml->writing_streamheader) { GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_IN_CAPS); } if (!is_keyframe) { GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); } ebml->last_pos = ebml->pos; ebml->last_write_result = gst_pad_push (ebml->srcpad, buffer); } else { gst_buffer_unref (buffer); } } /** * gst_ebml_write_element_new: * @ebml: a #GstEbmlWrite. * @size: size of the requested buffer. * * Create a buffer for one element. If there is * a cache, use that instead. * * Returns: A new #GstBuffer. */ static GstBuffer * gst_ebml_write_element_new (GstEbmlWrite * ebml, guint8 ** data_out, guint size) { /* Create new buffer of size + ID + length */ GstBuffer *buf; /* length, ID */ size += 12; buf = gst_buffer_new_and_alloc (size); GST_BUFFER_TIMESTAMP (buf) = ebml->timestamp; *data_out = gst_buffer_map (buf, NULL, NULL, GST_MAP_WRITE); return buf; } /** * gst_ebml_write_element_id: * @data_inout: Pointer to data pointer * @id: Element ID that should be written. * * Write element ID into a buffer. */ static void gst_ebml_write_element_id (guint8 ** data_inout, guint32 id) { guint8 *data = *data_inout; 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 */ *data_inout += bytes; while (bytes--) { data[bytes] = id & 0xff; id >>= 8; } } /** * gst_ebml_write_element_size: * @data_inout: Pointer to data pointer * @size: Element length. * * Write element length into a buffer. */ static void gst_ebml_write_element_size (guint8 ** data_inout, guint64 size) { guint8 *data = *data_inout; guint bytes = 1, mask = 0x80; if (size != GST_EBML_SIZE_UNKNOWN) { /* how many bytes? - use mask-1 because an all-1 bitset is not allowed */ while ((size >> ((bytes - 1) * 8)) >= (mask - 1) && bytes <= 8) { mask >>= 1; bytes++; } /* if invalid size, use max. */ if (bytes > 8) { GST_WARNING ("Invalid size, writing size unknown"); mask = 0x01; bytes = 8; /* Now here's a real FIXME: we cannot read those yet! */ size = GST_EBML_SIZE_UNKNOWN; } } else { mask = 0x01; bytes = 8; } /* write out, BE, with length size marker */ *data_inout += bytes; while (bytes-- > 0) { data[bytes] = size & 0xff; size >>= 8; if (!bytes) *data |= mask; } } /** * gst_ebml_write_element_data: * @data_inout: Pointer to data pointer * @write: Data that should be written. * @length: Length of the data. * * Write element data into a buffer. */ static void gst_ebml_write_element_data (guint8 ** data_inout, guint8 * write, guint64 length) { memcpy (*data_inout, write, length); *data_inout += length; } /** * gst_ebml_write_element_push: * @ebml: #GstEbmlWrite * @buf: #GstBuffer to be written. * @buf_data: Start of data to push from @buf (or NULL for whole buffer). * @buf_data_end: Data pointer positioned after the last byte in @buf_data (or * NULL for whole buffer). * * Write out buffer by moving it to the next element. */ static void gst_ebml_write_element_push (GstEbmlWrite * ebml, GstBuffer * buf, guint8 * buf_data, guint8 * buf_data_end) { guint data_size; if (buf_data_end) data_size = buf_data_end - buf_data; else data_size = gst_buffer_get_size (buf); ebml->pos += data_size; /* if there's no cache, then don't push it! */ if (ebml->writing_streamheader) { if (!buf_data) buf_data = gst_buffer_map (buf, NULL, NULL, GST_MAP_WRITE); gst_byte_writer_put_data (ebml->streamheader, buf_data, data_size); } if (ebml->cache) { if (!buf_data) buf_data = gst_buffer_map (buf, NULL, NULL, GST_MAP_WRITE); gst_byte_writer_put_data (ebml->cache, buf_data, data_size); gst_buffer_unmap (buf, buf_data, -1); gst_buffer_unref (buf); return; } if (buf_data) gst_buffer_unmap (buf, buf_data, -1); if (ebml->last_write_result == GST_FLOW_OK) { buf = gst_buffer_make_writable (buf); GST_BUFFER_OFFSET (buf) = ebml->pos - data_size; GST_BUFFER_OFFSET_END (buf) = ebml->pos; if (ebml->writing_streamheader) { GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS); } GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); if (GST_BUFFER_OFFSET (buf) != ebml->last_pos) { gst_ebml_writer_send_segment_event (ebml, GST_BUFFER_OFFSET (buf)); GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); } ebml->last_pos = ebml->pos; ebml->last_write_result = gst_pad_push (ebml->srcpad, buf); } else { gst_buffer_unref (buf); } } /** * gst_ebml_write_seek: * @ebml: #GstEbmlWrite * @pos: Seek position. * * Seek. */ void gst_ebml_write_seek (GstEbmlWrite * ebml, guint64 pos) { if (ebml->writing_streamheader) { GST_DEBUG ("wanting to seek to pos %" G_GUINT64_FORMAT, pos); if (pos >= ebml->streamheader_pos && pos <= ebml->streamheader_pos + ebml->streamheader->parent.size) { gst_byte_writer_set_pos (ebml->streamheader, pos - ebml->streamheader_pos); GST_DEBUG ("seeked in streamheader to position %" G_GUINT64_FORMAT, pos - ebml->streamheader_pos); } else { GST_WARNING ("we are writing streamheader still and seek is out of bounds"); } } /* Cache seeking. A bit dangerous, we assume the client writer * knows what he's doing... */ if (ebml->cache) { /* within bounds? */ if (pos >= ebml->cache_pos && pos <= ebml->cache_pos + ebml->cache->parent.size) { GST_DEBUG ("seeking in cache to %" G_GUINT64_FORMAT, pos); ebml->pos = pos; gst_byte_writer_set_pos (ebml->cache, ebml->pos - ebml->cache_pos); return; } else { GST_LOG ("Seek outside cache range. Clearing..."); gst_ebml_write_flush_cache (ebml, FALSE, GST_CLOCK_TIME_NONE); } } GST_INFO ("scheduling seek to %" G_GUINT64_FORMAT, pos); ebml->pos = pos; } /** * gst_ebml_write_get_uint_size: * @num: Number to be encoded. * * Get number of bytes needed to write a uint. * * Returns: Encoded uint length. */ static guint gst_ebml_write_get_uint_size (guint64 num) { guint size = 1; /* get size */ while (num >= (G_GINT64_CONSTANT (1) << (size * 8)) && size < 8) { size++; } return size; } /** * gst_ebml_write_set_uint: * @data_inout: Pointer to data pointer * @num: Number to be written. * @size: Encoded number length. * * Write an uint into a buffer. */ static void gst_ebml_write_set_uint (guint8 ** data_inout, guint64 num, guint size) { guint8 *data = *data_inout; *data_inout += size; while (size-- > 0) { data[size] = num & 0xff; num >>= 8; } } /** * gst_ebml_write_uint: * @ebml: #GstEbmlWrite * @id: Element ID. * @num: Number to be written. * * Write uint element. */ void gst_ebml_write_uint (GstEbmlWrite * ebml, guint32 id, guint64 num) { GstBuffer *buf; guint8 *data_start, *data_end; guint size = gst_ebml_write_get_uint_size (num); buf = gst_ebml_write_element_new (ebml, &data_start, sizeof (num)); data_end = data_start; /* write */ gst_ebml_write_element_id (&data_end, id); gst_ebml_write_element_size (&data_end, size); gst_ebml_write_set_uint (&data_end, num, size); gst_ebml_write_element_push (ebml, buf, data_start, data_end); } /** * gst_ebml_write_sint: * @ebml: #GstEbmlWrite * @id: Element ID. * @num: Number to be written. * * Write sint element. */ void gst_ebml_write_sint (GstEbmlWrite * ebml, guint32 id, gint64 num) { GstBuffer *buf; guint8 *data_start, *data_end; /* 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); buf = gst_ebml_write_element_new (ebml, &data_start, sizeof (num)); data_end = data_start; /* make unsigned */ if (num >= 0) { unum = num; } else { unum = 0x80 << (size - 1); unum += num; unum |= 0x80 << (size - 1); } /* write */ gst_ebml_write_element_id (&data_end, id); gst_ebml_write_element_size (&data_end, size); gst_ebml_write_set_uint (&data_end, unum, size); gst_ebml_write_element_push (ebml, buf, data_start, data_end); } /** * gst_ebml_write_float: * @ebml: #GstEbmlWrite * @id: Element ID. * @num: Number to be written. * * Write float element. */ void gst_ebml_write_float (GstEbmlWrite * ebml, guint32 id, gdouble num) { GstBuffer *buf; guint8 *data_start, *data_end; buf = gst_ebml_write_element_new (ebml, &data_start, sizeof (num)); data_end = data_start; gst_ebml_write_element_id (&data_end, id); gst_ebml_write_element_size (&data_end, 8); num = GDOUBLE_TO_BE (num); gst_ebml_write_element_data (&data_end, (guint8 *) & num, 8); gst_ebml_write_element_push (ebml, buf, data_start, data_end); } /** * gst_ebml_write_ascii: * @ebml: #GstEbmlWrite * @id: Element ID. * @str: String to be written. * * Write string element. */ void gst_ebml_write_ascii (GstEbmlWrite * ebml, guint32 id, const gchar * str) { gint len = strlen (str) + 1; /* add trailing '\0' */ GstBuffer *buf; guint8 *data_start, *data_end; buf = gst_ebml_write_element_new (ebml, &data_start, len); data_end = data_start; gst_ebml_write_element_id (&data_end, id); gst_ebml_write_element_size (&data_end, len); gst_ebml_write_element_data (&data_end, (guint8 *) str, len); gst_ebml_write_element_push (ebml, buf, data_start, data_end); } /** * gst_ebml_write_utf8: * @ebml: #GstEbmlWrite * @id: Element ID. * @str: String to be written. * * Write utf8 encoded string element. */ void gst_ebml_write_utf8 (GstEbmlWrite * ebml, guint32 id, const gchar * str) { gst_ebml_write_ascii (ebml, id, str); } /** * gst_ebml_write_date: * @ebml: #GstEbmlWrite * @id: Element ID. * @date: Date in seconds since the unix epoch. * * Write date element. */ void gst_ebml_write_date (GstEbmlWrite * ebml, guint32 id, gint64 date) { gst_ebml_write_sint (ebml, id, (date - GST_EBML_DATE_OFFSET) * GST_SECOND); } /** * gst_ebml_write_master_start: * @ebml: #GstEbmlWrite * @id: Element ID. * * Start wiriting mater element. * * Master writing is annoying. We use a size marker of * the max. allowed length, so that we can later fill it * in validly. * * Returns: Master starting position. */ guint64 gst_ebml_write_master_start (GstEbmlWrite * ebml, guint32 id) { guint64 pos = ebml->pos; GstBuffer *buf; guint8 *data_start, *data_end; buf = gst_ebml_write_element_new (ebml, &data_start, 0); data_end = data_start; gst_ebml_write_element_id (&data_end, id); pos += data_end - data_start; gst_ebml_write_element_size (&data_end, GST_EBML_SIZE_UNKNOWN); gst_ebml_write_element_push (ebml, buf, data_start, data_end); return pos; } /** * gst_ebml_write_master_finish_full: * @ebml: #GstEbmlWrite * @startpos: Master starting position. * * Finish writing master element. Size of master element is difference between * current position and the element start, and @extra_size added to this. */ void gst_ebml_write_master_finish_full (GstEbmlWrite * ebml, guint64 startpos, guint64 extra_size) { guint64 pos = ebml->pos; guint8 *data = g_malloc (8); GstBuffer *buf = gst_buffer_new_wrapped (data, 8); gst_ebml_write_seek (ebml, startpos); GST_WRITE_UINT64_BE (data, (G_GINT64_CONSTANT (1) << 56) | (pos - startpos - 8 + extra_size)); gst_ebml_write_element_push (ebml, buf, NULL, NULL); gst_ebml_write_seek (ebml, pos); } void gst_ebml_write_master_finish (GstEbmlWrite * ebml, guint64 startpos) { gst_ebml_write_master_finish_full (ebml, startpos, 0); } /** * gst_ebml_write_binary: * @ebml: #GstEbmlWrite * @id: Element ID. * @binary: Data to be written. * @length: Length of the data * * Write an element with binary data. */ void gst_ebml_write_binary (GstEbmlWrite * ebml, guint32 id, guint8 * binary, guint64 length) { GstBuffer *buf; guint8 *data_start, *data_end; buf = gst_ebml_write_element_new (ebml, &data_start, length); data_end = data_start; gst_ebml_write_element_id (&data_end, id); gst_ebml_write_element_size (&data_end, length); gst_ebml_write_element_data (&data_end, binary, length); gst_ebml_write_element_push (ebml, buf, data_start, data_end); } /** * gst_ebml_write_buffer_header: * @ebml: #GstEbmlWrite * @id: Element ID. * @length: Length of the data * * Write header of the binary element (use with gst_ebml_write_buffer function). * * 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; guint8 *data_start, *data_end; buf = gst_ebml_write_element_new (ebml, &data_start, 0); data_end = data_start; gst_ebml_write_element_id (&data_end, id); gst_ebml_write_element_size (&data_end, length); gst_ebml_write_element_push (ebml, buf, data_start, data_end); } /** * gst_ebml_write_buffer: * @ebml: #GstEbmlWrite * @buf: #GstBuffer cointaining the data. * * Write binary element (see gst_ebml_write_buffer_header). */ void gst_ebml_write_buffer (GstEbmlWrite * ebml, GstBuffer * buf) { gst_ebml_write_element_push (ebml, buf, NULL, NULL); } /** * gst_ebml_replace_uint: * @ebml: #GstEbmlWrite * @pos: Position of the uint that should be replaced. * @num: New value. * * Replace uint with a new value. * * 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; guint8 *data_start, *data_end; GstBuffer *buf; data_start = g_malloc (8); data_end = data_start; buf = gst_buffer_new_wrapped (data_start, 8); gst_ebml_write_seek (ebml, pos); gst_ebml_write_set_uint (&data_end, num, 8); gst_ebml_write_element_push (ebml, buf, data_start, data_end); gst_ebml_write_seek (ebml, oldpos); } /** * gst_ebml_write_header: * @ebml: #GstEbmlWrite * @doctype: Document type. * @version: Document type version. * * Write EBML header. */ void gst_ebml_write_header (GstEbmlWrite * ebml, const 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, FALSE, 0); }