mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-10 03:19:40 +00:00
99a6f8207f
Don't carry over DISCONT flags from the input buffers to the output buffer, or the demuxer might reset its state when it receives the first data buffer just after parsing the simple block header, and then expect sane data to follow. Fixes matroskamux ! demux erroring out. https://bugzilla.gnome.org/show_bug.cgi?id=754768 https://bugzilla.gnome.org/show_bug.cgi?id=657805
936 lines
23 KiB
C
936 lines
23 KiB
C
/* 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, 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);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|