| /* GStreamer EBML I/O |
| * (c) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net> |
| * (c) 2005 Michal Benes <michal.benes@xeris.cz> |
| * |
| * 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., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <string.h> |
| |
| #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 %" G_GSIZE_FORMAT, |
| 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, |
| ebml->streamable ? GST_FORMAT_TIME : 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 %" G_GSIZE_FORMAT, |
| 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); |
| } else { |
| GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DISCONT); |
| } |
| if (ebml->writing_streamheader) { |
| GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_HEADER); |
| } else { |
| GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_HEADER); |
| } |
| 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, GstMapInfo * map, 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; |
| |
| /* FIXME unmap not possible */ |
| gst_buffer_map (buf, map, 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 (bytes <= 8 && (size >> ((bytes - 1) * 8)) >= (mask - 1)) { |
| 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) |
| { |
| GstMapInfo map; |
| guint data_size; |
| |
| map.data = NULL; |
| |
| 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) { |
| gst_buffer_map (buf, &map, GST_MAP_READ); |
| buf_data = map.data; |
| } |
| if (!buf_data) |
| GST_WARNING ("Failed to map buffer"); |
| else if (!gst_byte_writer_put_data (ebml->streamheader, buf_data, |
| data_size)) |
| GST_WARNING ("Error writing data to streamheader"); |
| } |
| if (ebml->cache) { |
| if (!buf_data) { |
| gst_buffer_map (buf, &map, GST_MAP_READ); |
| buf_data = map.data; |
| } |
| if (!buf_data) |
| GST_WARNING ("Failed to map buffer"); |
| else if (!gst_byte_writer_put_data (ebml->cache, buf_data, data_size)) |
| GST_WARNING ("Error writing data to cache"); |
| if (map.data) |
| gst_buffer_unmap (buf, &map); |
| gst_buffer_unref (buf); |
| return; |
| } |
| |
| if (buf_data && map.data) |
| gst_buffer_unmap (buf, &map); |
| |
| 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_HEADER); |
| } else { |
| GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_HEADER); |
| } |
| 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); |
| } else { |
| GST_BUFFER_FLAG_UNSET (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 (size < 8 && num >= (G_GINT64_CONSTANT (1) << (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); |
| GstMapInfo map; |
| |
| buf = gst_ebml_write_element_new (ebml, &map, sizeof (num)); |
| data_end = data_start = map.data; |
| |
| /* 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_buffer_unmap (buf, &map); |
| gst_buffer_set_size (buf, (data_end - data_start)); |
| |
| 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; |
| GstMapInfo map; |
| |
| /* 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, &map, sizeof (num)); |
| data_end = data_start = map.data; |
| |
| /* make unsigned */ |
| if (num >= 0) { |
| unum = num; |
| } else { |
| unum = ((guint64) 0x80) << ((size - 1) * 8); |
| unum += num; |
| unum |= ((guint64) 0x80) << ((size - 1) * 8); |
| } |
| |
| /* 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_buffer_unmap (buf, &map); |
| gst_buffer_set_size (buf, (data_end - data_start)); |
| |
| 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; |
| GstMapInfo map; |
| guint8 *data_start, *data_end; |
| |
| buf = gst_ebml_write_element_new (ebml, &map, sizeof (num)); |
| data_end = data_start = map.data; |
| |
| 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_buffer_unmap (buf, &map); |
| gst_buffer_set_size (buf, (data_end - data_start)); |
| |
| 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; |
| GstMapInfo map; |
| guint8 *data_start, *data_end; |
| |
| buf = gst_ebml_write_element_new (ebml, &map, len); |
| data_end = data_start = map.data; |
| |
| 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_buffer_unmap (buf, &map); |
| gst_buffer_set_size (buf, (data_end - data_start)); |
| |
| 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; |
| GstMapInfo map; |
| guint8 *data_start, *data_end; |
| |
| buf = gst_ebml_write_element_new (ebml, &map, 0); |
| data_end = data_start = map.data; |
| |
| 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_buffer_unmap (buf, &map); |
| gst_buffer_set_size (buf, (data_end - data_start)); |
| |
| 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; |
| GstMapInfo map; |
| guint8 *data_start, *data_end; |
| |
| buf = gst_ebml_write_element_new (ebml, &map, length); |
| data_end = data_start = map.data; |
| |
| 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_buffer_unmap (buf, &map); |
| gst_buffer_set_size (buf, (data_end - data_start)); |
| |
| 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; |
| GstMapInfo map; |
| guint8 *data_start, *data_end; |
| |
| buf = gst_ebml_write_element_new (ebml, &map, 0); |
| data_end = data_start = map.data; |
| |
| gst_ebml_write_element_id (&data_end, id); |
| gst_ebml_write_element_size (&data_end, length); |
| gst_buffer_unmap (buf, &map); |
| gst_buffer_set_size (buf, (data_end - data_start)); |
| |
| 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); |
| } |