#include "matroska-mux.h"
#include "matroska-ids.h"
+#define GST_MATROSKA_MUX_CHAPLANG "und"
+
GST_DEBUG_CATEGORY_STATIC (matroskamux_debug);
#define GST_CAT_DEFAULT matroskamux_debug
#define parent_class gst_matroska_mux_parent_class
G_DEFINE_TYPE_WITH_CODE (GstMatroskaMux, gst_matroska_mux, GST_TYPE_ELEMENT,
- G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL));
+ G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL)
+ G_IMPLEMENT_INTERFACE (GST_TYPE_TOC_SETTER, NULL)
+ );
/* Matroska muxer destructor */
static void gst_matroska_mux_finalize (GObject * object);
gst_static_pad_template_get (&subtitlesink_templ));
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&src_templ));
- gst_element_class_set_details_simple (gstelement_class, "Matroska muxer",
+ gst_element_class_set_static_metadata (gstelement_class, "Matroska muxer",
"Codec/Muxer",
"Muxes video/audio/subtitle streams into a matroska stream",
"GStreamer maintainers <gstreamer-devel@lists.sourceforge.net>");
/* reset tags */
gst_tag_setter_reset_tags (GST_TAG_SETTER (mux));
+
+ mux->tags_pos = 0;
+
+ /* reset chapters */
+ gst_toc_setter_reset_toc (GST_TOC_SETTER (mux));
+
+ mux->chapters_pos = 0;
}
/**
GstMatroskaMux *mux;
GstPad *pad;
GstTagList *list;
- gboolean ret = FALSE;
+ gboolean ret = TRUE;
mux = GST_MATROSKA_MUX (user_data);
collect_pad = (GstMatroskaPad *) data;
gst_event_unref (event);
/* handled this, don't want collectpads to forward it downstream */
event = NULL;
+ ret = TRUE;
break;
}
- case GST_EVENT_SEGMENT:{
- const GstSegment *segment;
+ case GST_EVENT_TOC:{
+ GstToc *toc;
- gst_event_parse_segment (event, &segment);
- if (segment->format != GST_FORMAT_TIME) {
- gst_event_unref (event);
- event = NULL;
+ if (mux->chapters_pos > 0)
+ break;
+
+ GST_DEBUG_OBJECT (mux, "received toc event");
+ gst_event_parse_toc (event, &toc, NULL);
+
+ if (toc != NULL) {
+ if (gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL) {
+ gst_toc_setter_reset_toc (GST_TOC_SETTER (mux));
+ GST_INFO_OBJECT (pad, "Replacing TOC with a new one");
+ }
+
+ gst_toc_setter_set_toc (GST_TOC_SETTER (mux), toc);
+ gst_toc_free (toc);
}
+
+ gst_event_unref (event);
+ /* handled this, don't want collectpads to forward it downstream */
+ event = NULL;
break;
}
case GST_EVENT_CUSTOM_DOWNSTREAM:{
/* transform into private data for stream; text form */
gst_matroska_mux_build_vobsub_private (context, clut);
}
- break;
}
+ /* fall through */
default:
break;
}
- /* now GstCollectPads2 can take care of the rest, e.g. EOS */
+ if (event != NULL)
+ return gst_collect_pads2_event_default (pads, data, event, FALSE);
+
return ret;
}
} else {
if (gst_buffer_memcmp (buf0, 1, "vorbis", 6) == 0) {
GstMatroskaTrackAudioContext *audiocontext;
- guint8 *data, *hdr;
+ GstMapInfo map;
+ guint8 *hdr;
- data = gst_buffer_map (buf0, NULL, NULL, GST_MAP_READ);
- hdr = data + 1 + 6 + 4;
+ gst_buffer_map (buf0, &map, GST_MAP_READ);
+ hdr = map.data + 1 + 6 + 4;
audiocontext = (GstMatroskaTrackAudioContext *) context;
audiocontext->channels = GST_READ_UINT8 (hdr);
audiocontext->samplerate = GST_READ_UINT32_LE (hdr + 1);
- gst_buffer_unmap (buf0, data, -1);
+ gst_buffer_unmap (buf0, &map);
}
}
} else {
GstMatroskaTrackVideoContext *videocontext;
guint fps_num, fps_denom, par_num, par_denom;
- guint8 *data, *hdr;
+ GstMapInfo map;
+ guint8 *hdr;
- data = gst_buffer_map (buf0, NULL, NULL, GST_MAP_READ);
- hdr = data + 1 + 6 + 3 + 2 + 2;
+ gst_buffer_map (buf0, &map, GST_MAP_READ);
+ hdr = map.data + 1 + 6 + 3 + 2 + 2;
videocontext = (GstMatroskaTrackVideoContext *) context;
videocontext->pixel_width = GST_READ_UINT32_BE (hdr) >> 8;
}
hdr += 3 + 3;
- gst_buffer_unmap (buf0, data, -1);
+ gst_buffer_unmap (buf0, &map);
}
if (buf0)
GstStructure *structure;
const GValue *value = NULL;
GstBuffer *buf = NULL;
- gchar *id = NULL;
gboolean ret = TRUE;
mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
structure = gst_caps_get_structure (caps, 0);
mimetype = gst_structure_get_name (structure);
- /* keep track of default set in request_pad */
- id = context->codec_id;
-
/* general setup */
scontext->check_utf8 = 1;
scontext->invalid_utf8 = 0;
gst_matroska_mux_set_codec_id (context,
GST_MATROSKA_CODEC_ID_SUBTITLE_VOBSUB);
} else {
- id = NULL;
ret = FALSE;
goto exit;
}
if (value)
buf = gst_value_get_buffer (value);
if (buf != NULL) {
- guint8 *priv_data = NULL, *priv_buffer_data;
- gsize priv_data_size = 0;
+ GstMapInfo map;
+ guint8 *priv_data = NULL;
+
+ gst_buffer_map (buf, &map, GST_MAP_READ);
- priv_buffer_data =
- gst_buffer_map (buf, &priv_data_size, NULL, GST_MAP_READ);
- if (priv_data_size > SUBTITLE_MAX_CODEC_PRIVATE) {
+ if (map.size > SUBTITLE_MAX_CODEC_PRIVATE) {
GST_WARNING_OBJECT (mux, "pad %" GST_PTR_FORMAT " subtitle private data"
" exceeded maximum (%d); discarding", pad,
SUBTITLE_MAX_CODEC_PRIVATE);
- gst_buffer_unmap (buf, priv_data, priv_data_size);
+ gst_buffer_unmap (buf, &map);
return TRUE;
}
gst_matroska_mux_free_codec_priv (context);
- priv_data = g_malloc0 (priv_data_size);
- memcpy (priv_data, priv_buffer_data, priv_data_size);
+ priv_data = g_malloc0 (map.size);
+ memcpy (priv_data, map.data, map.size);
context->codec_priv = priv_data;
- context->codec_priv_size = priv_data_size;
- gst_buffer_unmap (buf, priv_buffer_data, priv_data_size);
+ context->codec_priv_size = map.size;
+ gst_buffer_unmap (buf, &map);
}
- GST_DEBUG_OBJECT (pad, "codec_id %s, codec data size %u",
+ GST_DEBUG_OBJECT (pad, "codec_id %s, codec data size %" G_GSIZE_FORMAT,
GST_STR_NULL (context->codec_id), context->codec_priv_size);
exit:
- /* free default if modified */
- if (id)
- g_free (id);
return ret;
}
context->codec_priv, context->codec_priv_size);
}
+static void
+gst_matroska_mux_write_chapter_title (const gchar * title, GstEbmlWrite * ebml)
+{
+ guint64 title_master;
+
+ title_master =
+ gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CHAPTERDISPLAY);
+
+ gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CHAPSTRING, title);
+ gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CHAPLANGUAGE,
+ GST_MATROSKA_MUX_CHAPLANG);
+
+ gst_ebml_write_master_finish (ebml, title_master);
+}
+
+static void
+gst_matroska_mux_write_chapter (GstMatroskaMux * mux, GstTocEntry * edition,
+ GstTocEntry * entry, GstEbmlWrite * ebml, guint64 * master_chapters,
+ guint64 * master_edition)
+{
+ guint64 uid, master_chapteratom;
+ GList *cur;
+ GstTocEntry *cur_entry;
+ guint count, i;
+ gchar *title;
+ gint64 start, stop;
+
+ if (G_UNLIKELY (master_chapters != NULL && *master_chapters == 0))
+ *master_chapters =
+ gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CHAPTERS);
+
+ if (G_UNLIKELY (master_edition != NULL && *master_edition == 0)) {
+ /* create uid for the parent */
+ uid = gst_matroska_mux_create_uid ();
+ g_free (edition->uid);
+ edition->uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uid);
+
+ *master_edition =
+ gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_EDITIONENTRY);
+
+ gst_ebml_write_uint (ebml, GST_MATROSKA_ID_EDITIONUID, uid);
+ gst_ebml_write_uint (ebml, GST_MATROSKA_ID_EDITIONFLAGHIDDEN, 0);
+ gst_ebml_write_uint (ebml, GST_MATROSKA_ID_EDITIONFLAGDEFAULT, 0);
+ gst_ebml_write_uint (ebml, GST_MATROSKA_ID_EDITIONFLAGORDERED, 0);
+ }
+
+ uid = gst_matroska_mux_create_uid ();
+ gst_toc_entry_get_start_stop (entry, &start, &stop);
+
+ master_chapteratom =
+ gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CHAPTERATOM);
+ g_free (entry->uid);
+ entry->uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uid);
+ gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERUID, uid);
+ gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERTIMESTART, start);
+ gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERTIMESTOP, stop);
+ gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERFLAGHIDDEN, 0);
+ gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERFLAGENABLED, 1);
+
+ cur = entry->subentries;
+ while (cur != NULL) {
+ cur_entry = cur->data;
+ gst_matroska_mux_write_chapter (mux, NULL, cur_entry, ebml, NULL, NULL);
+
+ cur = cur->next;
+ }
+
+ if (G_LIKELY (entry->tags != NULL)) {
+ count = gst_tag_list_get_tag_size (entry->tags, GST_TAG_TITLE);
+
+ for (i = 0; i < count; ++i) {
+ gst_tag_list_get_string_index (entry->tags, GST_TAG_TITLE, i, &title);
+ gst_matroska_mux_write_chapter_title (title, ebml);
+ g_free (title);
+ }
+
+ /* remove title tag */
+ if (G_LIKELY (count > 0))
+ gst_tag_list_remove_tag (entry->tags, GST_TAG_TITLE);
+ }
+
+ gst_ebml_write_master_finish (ebml, master_chapteratom);
+}
+
+static void
+gst_matroska_mux_write_chapter_edition (GstMatroskaMux * mux,
+ GstTocEntry * entry, GstEbmlWrite * ebml, guint64 * master_chapters)
+{
+ guint64 master_edition = 0;
+ GList *cur;
+ GstTocEntry *subentry;
+
+ cur = entry->subentries;
+ while (cur != NULL) {
+ subentry = cur->data;
+ gst_matroska_mux_write_chapter (mux, entry, subentry, ebml, master_chapters,
+ &master_edition);
+
+ cur = cur->next;
+ }
+
+ if (G_LIKELY (master_edition != 0))
+ gst_ebml_write_master_finish (ebml, master_edition);
+}
/**
* gst_matroska_mux_start:
const gchar *doctype;
guint32 seekhead_id[] = { GST_MATROSKA_ID_SEGMENTINFO,
GST_MATROSKA_ID_TRACKS,
+ GST_MATROSKA_ID_CHAPTERS,
GST_MATROSKA_ID_CUES,
GST_MATROSKA_ID_TAGS,
0
guint32 segment_uid[4];
GTimeVal time = { 0, 0 };
+ /* if not streaming, check if downstream is seekable */
+ if (!mux->streamable) {
+ gboolean seekable;
+ GstQuery *query;
+
+ query = gst_query_new_seeking (GST_FORMAT_BYTES);
+ if (gst_pad_peer_query (mux->srcpad, query)) {
+ gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
+ GST_INFO_OBJECT (mux, "downstream is %sseekable", seekable ? "" : "not ");
+ } else {
+ /* have to assume seeking is supported if query not handled downstream */
+ GST_WARNING_OBJECT (mux, "downstream did not handle seeking query");
+ seekable = FALSE;
+ }
+ if (!seekable) {
+ mux->streamable = TRUE;
+ g_object_notify (G_OBJECT (mux), "streamable");
+ GST_WARNING_OBJECT (mux, "downstream is not seekable, but "
+ "streamable=false. Will ignore that and create streamable output "
+ "instead");
+ }
+ gst_query_unref (query);
+ }
+
if (!strcmp (mux->doctype, GST_MATROSKA_DOCTYPE_WEBM)) {
ebml->caps = gst_caps_new_empty_simple ("video/webm");
} else {
}
gst_ebml_write_master_finish (ebml, master);
+ /* chapters */
+ if (gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL && !mux->streamable) {
+ guint64 master_chapters = 0;
+ GstTocEntry *toc_entry;
+ const GstToc *toc;
+ GList *cur, *to_write = NULL;
+ gint64 start, stop;
+
+ GST_DEBUG ("Writing chapters");
+
+ toc = gst_toc_setter_get_toc (GST_TOC_SETTER (mux));
+
+ /* check whether we have editions or chapters at the root level */
+ toc_entry = toc->entries->data;
+
+ if (toc_entry->type != GST_TOC_ENTRY_TYPE_EDITION) {
+ toc_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, "");
+ gst_toc_entry_set_start_stop (toc_entry, -1, -1);
+
+ /* aggregate all chapters without root edition */
+ cur = toc->entries;
+ while (cur != NULL) {
+ toc_entry->subentries =
+ g_list_prepend (toc_entry->subentries, cur->data);
+ cur = cur->next;
+ }
+
+ gst_toc_entry_get_start_stop (((GstTocEntry *) toc_entry->
+ subentries->data), &start, NULL);
+ toc_entry->subentries = g_list_reverse (toc_entry->subentries);
+ gst_toc_entry_get_start_stop (((GstTocEntry *) toc_entry->
+ subentries->data), NULL, &stop);
+ gst_toc_entry_set_start_stop (toc_entry, start, stop);
+
+ to_write = g_list_append (to_write, toc_entry);
+ } else {
+ toc_entry = NULL;
+ to_write = toc->entries;
+ }
+
+ /* finally write chapters */
+ mux->chapters_pos = ebml->pos;
+
+ cur = to_write;
+ while (cur != NULL) {
+ gst_matroska_mux_write_chapter_edition (mux, cur->data, ebml,
+ &master_chapters);
+ cur = cur->next;
+ }
+
+ /* close master element if any edition was written */
+ if (G_LIKELY (master_chapters != 0))
+ gst_ebml_write_master_finish (ebml, master_chapters);
+
+ if (toc_entry != NULL) {
+ g_list_free (toc_entry->subentries);
+ toc_entry->subentries = NULL;
+ gst_toc_entry_free (toc_entry);
+ g_list_free (to_write);
+ }
+ }
+
/* lastly, flush the cache */
gst_ebml_write_flush_cache (ebml, FALSE, 0);
}
}
}
+static void
+gst_matroska_mux_write_toc_entry_tags (GstMatroskaMux * mux,
+ const GstTocEntry * entry, guint64 * master_tags)
+{
+ guint64 master_tag, master_targets;
+ GstEbmlWrite *ebml;
+ GList *cur;
+
+ ebml = mux->ebml_write;
+
+ if (G_UNLIKELY (entry->tags != NULL && !gst_tag_list_is_empty (entry->tags))) {
+ if (*master_tags == 0) {
+ mux->tags_pos = ebml->pos;
+ *master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS);
+ }
+
+ master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG);
+ master_targets =
+ gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TARGETS);
+
+ if (entry->type == GST_TOC_ENTRY_TYPE_EDITION)
+ gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TARGETEDITIONUID,
+ g_ascii_strtoull (entry->uid, NULL, 10));
+ else
+ gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TARGETCHAPTERUID,
+ g_ascii_strtoull (entry->uid, NULL, 10));
+
+ gst_ebml_write_master_finish (ebml, master_targets);
+ gst_tag_list_foreach (entry->tags, gst_matroska_mux_write_simple_tag, ebml);
+ gst_ebml_write_master_finish (ebml, master_tag);
+ }
+
+ cur = entry->subentries;
+ while (cur != NULL) {
+ gst_matroska_mux_write_toc_entry_tags (mux, cur->data, master_tags);
+ cur = cur->next;
+ }
+}
/**
* gst_matroska_mux_finish:
/* tags */
tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (mux));
- if (tags != NULL && !gst_tag_list_is_empty (tags)) {
- guint64 master_tags, master_tag;
+ if ((tags != NULL && !gst_tag_list_is_empty (tags))
+ || gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL) {
+ guint64 master_tags = 0, master_tag;
+ GList *cur;
+ const GstToc *toc;
GST_DEBUG_OBJECT (mux, "Writing tags");
- /* TODO: maybe limit via the TARGETS id by looking at the source pad */
- mux->tags_pos = ebml->pos;
- master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS);
- master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG);
- gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml);
- gst_ebml_write_master_finish (ebml, master_tag);
- gst_ebml_write_master_finish (ebml, master_tags);
+ toc = gst_toc_setter_get_toc (GST_TOC_SETTER (mux));
+
+ if (tags != NULL) {
+ /* TODO: maybe limit via the TARGETS id by looking at the source pad */
+ mux->tags_pos = ebml->pos;
+ master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS);
+ master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG);
+
+ if (tags != NULL)
+ gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml);
+ if (toc != NULL)
+ gst_tag_list_foreach (toc->tags, gst_matroska_mux_write_simple_tag,
+ ebml);
+
+ gst_ebml_write_master_finish (ebml, master_tag);
+ }
+
+ if (toc != NULL) {
+ cur = toc->entries;
+ while (cur != NULL) {
+ gst_matroska_mux_write_toc_entry_tags (mux, cur->data, &master_tags);
+ cur = cur->next;
+ }
+ }
+
+ if (master_tags != 0)
+ gst_ebml_write_master_finish (ebml, master_tags);
}
/* update seekhead. We know that:
- * - a seekhead contains 4 entries.
+ * - a seekhead contains 5 entries.
* - order of entries is as above.
* - a seekhead has a 4-byte header + 8-byte length
* - each entry is 2-byte master, 2-byte ID pointer,
mux->info_pos - mux->segment_master);
gst_ebml_replace_uint (ebml, mux->seekhead_pos + 60,
mux->tracks_pos - mux->segment_master);
- if (mux->index != NULL) {
+ if (gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL
+ && mux->chapters_pos > 0) {
gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88,
- mux->cues_pos - mux->segment_master);
+ mux->chapters_pos - mux->segment_master);
} else {
/* void'ify */
guint64 my_pos = ebml->pos;
gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
gst_ebml_write_seek (ebml, my_pos);
}
- if (tags != NULL) {
+ if (mux->index != NULL) {
gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116,
- mux->tags_pos - mux->segment_master);
+ mux->cues_pos - mux->segment_master);
} else {
/* void'ify */
guint64 my_pos = ebml->pos;
gst_ebml_write_seek (ebml, my_pos);
}
+ if (tags != NULL) {
+ gst_ebml_replace_uint (ebml, mux->seekhead_pos + 144,
+ mux->tags_pos - mux->segment_master);
+ } else {
+ /* void'ify */
+ guint64 my_pos = ebml->pos;
+
+ gst_ebml_write_seek (ebml, mux->seekhead_pos + 124);
+ gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
+ gst_ebml_write_seek (ebml, my_pos);
+ }
+
/* loop tracks:
* - first get the overall duration
* (a released track may have left a duration in here)
{
GstMatroskaTrackVideoContext *ctx =
(GstMatroskaTrackVideoContext *) collect_pad->track;
- guint8 *buf_data, *data;
+ GstMapInfo map;
+ guint8 *data;
gsize size;
guint8 parse_code;
guint32 next_parse_offset;
GstBuffer *ret = NULL;
gboolean is_muxing_unit = FALSE;
- buf_data = gst_buffer_map (buf, &size, NULL, GST_MAP_READ);
- data = buf_data;
+ gst_buffer_map (buf, &map, GST_MAP_READ);
+ data = map.data;
+ size = map.size;
if (size < 13) {
- gst_buffer_unmap (buf, buf_data, -1);
+ gst_buffer_unmap (buf, &map);
gst_buffer_unref (buf);
return ret;
}
/* Check if this buffer contains a picture or end-of-sequence packet */
while (size >= 13) {
if (GST_READ_UINT32_BE (data) != 0x42424344 /* 'BBCD' */ ) {
- gst_buffer_unmap (buf, buf_data, -1);
+ gst_buffer_unmap (buf, &map);
gst_buffer_unref (buf);
return ret;
}
}
if (ctx->dirac_unit)
- ctx->dirac_unit = gst_buffer_join (ctx->dirac_unit, gst_buffer_ref (buf));
+ ctx->dirac_unit = gst_buffer_append (ctx->dirac_unit, gst_buffer_ref (buf));
else
ctx->dirac_unit = gst_buffer_ref (buf);
- gst_buffer_unmap (buf, buf_data, -1);
+ gst_buffer_unmap (buf, &map);
if (is_muxing_unit) {
ret = gst_buffer_make_writable (ctx->dirac_unit);
s = gst_caps_get_structure (caps, 0);
g_value_init (&streamheader, GST_TYPE_ARRAY);
g_value_init (&bufval, GST_TYPE_BUFFER);
- GST_BUFFER_FLAG_SET (streamheader_buffer, GST_BUFFER_FLAG_IN_CAPS);
+ GST_BUFFER_FLAG_SET (streamheader_buffer, GST_BUFFER_FLAG_HEADER);
gst_value_set_buffer (&bufval, streamheader_buffer);
gst_value_array_append_value (&streamheader, &bufval);
g_value_unset (&bufval);