* (c) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
* (c) 2005 Michal Benes <michal.benes@xeris.cz>
* (c) 2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * (c) 2011 Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
*
* matroska-mux.c: matroska file/stream muxer
*
#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
GST_STATIC_CAPS ("audio/mpeg, "
"mpegversion = (int) 1, "
"layer = (int) [ 1, 3 ], "
- "stream-format = (string) { raw }, "
COMMON_AUDIO_CAPS "; "
"audio/mpeg, "
"mpegversion = (int) { 2, 4 }, "
+ "stream-format = (string) raw, "
COMMON_AUDIO_CAPS "; "
"audio/x-ac3, "
COMMON_AUDIO_CAPS "; "
COMMON_AUDIO_CAPS "; "
"audio/x-raw, "
"format = (string) { U8, S16BE, S16LE, S24BE, S24LE, S32BE, S32LE, F32LE, F64LE }, "
+ "layout = (string) interleaved, "
COMMON_AUDIO_CAPS ";"
"audio/x-tta, "
"width = (int) { 8, 16, 24 }, "
);
static GstStaticPadTemplate subtitlesink_templ =
-GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
+ GST_STATIC_PAD_TEMPLATE ("subtitle_%d",
GST_PAD_SINK,
GST_PAD_REQUEST,
- GST_STATIC_CAPS ("subtitle/x-kate"));
+ GST_STATIC_CAPS ("subtitle/x-kate; "
+ "text/plain; application/x-ssa; application/x-ass; "
+ "application/x-usf; video/x-dvd-subpicture; "
+ "application/x-subtitle-unknown")
+ );
static GArray *used_uids;
G_LOCK_DEFINE_STATIC (used_uids);
#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);
/* Pads collected callback */
-static GstFlowReturn
-gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data);
+static GstFlowReturn gst_matroska_mux_handle_buffer (GstCollectPads2 * pads,
+ GstCollectData2 * data, GstBuffer * buf, gpointer user_data);
+static gboolean gst_matroska_mux_handle_sink_event (GstCollectPads2 * pads,
+ GstCollectData2 * data, GstEvent * event, gpointer user_data);
/* pad functions */
static gboolean gst_matroska_mux_handle_src_event (GstPad * pad,
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>");
gst_pad_set_event_function (mux->srcpad, gst_matroska_mux_handle_src_event);
gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad);
- mux->collect = gst_collect_pads_new ();
- gst_collect_pads_set_function (mux->collect,
- (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_matroska_mux_collected),
- mux);
+ mux->collect = gst_collect_pads2_new ();
+ gst_collect_pads2_set_clip_function (mux->collect,
+ GST_DEBUG_FUNCPTR (gst_collect_pads2_clip_running_time), mux);
+ gst_collect_pads2_set_buffer_function (mux->collect,
+ GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_buffer), mux);
+ gst_collect_pads2_set_event_function (mux->collect,
+ GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_sink_event), mux);
mux->ebml_write = gst_ebml_write_new (mux->srcpad);
mux->doctype = GST_MATROSKA_DOCTYPE_MATROSKA;
collect_pad->track = NULL;
}
- /* free cached buffer */
- if (collect_pad->buffer != NULL) {
- gst_buffer_unref (collect_pad->buffer);
- collect_pad->buffer = NULL;
- }
-
if (!full && type != 0) {
GstMatroskaTrackContext *context;
/* TODO: check default values for the context */
context->flags = GST_MATROSKA_TRACK_ENABLED | GST_MATROSKA_TRACK_DEFAULT;
collect_pad->track = context;
- collect_pad->buffer = NULL;
collect_pad->duration = 0;
collect_pad->start_ts = GST_CLOCK_TIME_NONE;
collect_pad->end_ts = GST_CLOCK_TIME_NONE;
/* 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;
}
/**
return gst_pad_event_default (pad, parent, event);
}
+
+static void
+gst_matroska_mux_free_codec_priv (GstMatroskaTrackContext * context)
+{
+ if (context->codec_priv != NULL) {
+ g_free (context->codec_priv);
+ context->codec_priv = NULL;
+ context->codec_priv_size = 0;
+ }
+}
+
+static void
+gst_matroska_mux_build_vobsub_private (GstMatroskaTrackContext * context,
+ const guint * clut)
+{
+ gchar *clutv[17];
+ gchar *sclut;
+ gint i;
+ guint32 col;
+ gdouble y, u, v;
+ guint8 r, g, b;
+
+ /* produce comma-separated list in hex format */
+ for (i = 0; i < 16; ++i) {
+ col = clut[i];
+ /* replicate vobsub's slightly off RGB conversion calculation */
+ y = (((col >> 16) & 0xff) - 16) * 255 / 219;
+ u = ((col >> 8) & 0xff) - 128;
+ v = (col & 0xff) - 128;
+ r = CLAMP (1.0 * y + 1.4022 * u, 0, 255);
+ g = CLAMP (1.0 * y - 0.3456 * u - 0.7145 * v, 0, 255);
+ b = CLAMP (1.0 * y + 1.7710 * v, 0, 255);
+ clutv[i] = g_strdup_printf ("%02x%02x%02x", r, g, b);
+ }
+ clutv[i] = NULL;
+ sclut = g_strjoinv (",", clutv);
+
+ /* build codec private; only palette for now */
+ gst_matroska_mux_free_codec_priv (context);
+ context->codec_priv = (guint8 *) g_strdup_printf ("palette: %s", sclut);
+ /* include terminating 0 */
+ context->codec_priv_size = strlen ((gchar *) context->codec_priv) + 1;
+ g_free (sclut);
+ for (i = 0; i < 16; ++i) {
+ g_free (clutv[i]);
+ }
+}
+
+
/**
* gst_matroska_mux_handle_sink_event:
* @pad: Pad which received the event.
* Returns: #TRUE on success.
*/
static gboolean
-gst_matroska_mux_handle_sink_event (GstPad * pad, GstObject * parent,
- GstEvent * event)
+gst_matroska_mux_handle_sink_event (GstCollectPads2 * pads,
+ GstCollectData2 * data, GstEvent * event, gpointer user_data)
{
- GstMatroskaTrackContext *context;
GstMatroskaPad *collect_pad;
- GstMatroskaMux *mux = GST_MATROSKA_MUX (parent);
+ GstMatroskaTrackContext *context;
+ GstMatroskaMux *mux;
+ GstPad *pad;
GstTagList *list;
gboolean ret = TRUE;
+ mux = GST_MATROSKA_MUX (user_data);
+ collect_pad = (GstMatroskaPad *) data;
+ pad = data->pad;
+ context = collect_pad->track;
+ g_assert (context);
+
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:{
GstCaps *caps;
GST_DEBUG_OBJECT (mux, "received tag event");
gst_event_parse_tag (event, &list);
- collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
- g_assert (collect_pad);
- context = collect_pad->track;
- g_assert (context);
-
/* Matroska wants ISO 639-2B code, taglist most likely contains 639-1 */
if (gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &lang)) {
const gchar *lang_code;
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) {
- ret = FALSE;
- 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:{
gst_event_replace (&mux->force_key_unit_event, NULL);
mux->force_key_unit_event = event;
event = NULL;
+ } else if (gst_structure_has_name (structure, "application/x-gst-dvd") &&
+ !strcmp ("dvd-spu-clut-change",
+ gst_structure_get_string (structure, "event"))) {
+ gchar name[16];
+ gint i, value;
+ guint clut[16];
+
+ GST_DEBUG_OBJECT (pad, "New DVD colour table received");
+ if (context->type != GST_MATROSKA_TRACK_TYPE_SUBTITLE) {
+ GST_DEBUG_OBJECT (pad, "... discarding");
+ break;
+ }
+ /* first transform event data into table form */
+ for (i = 0; i < 16; i++) {
+ g_snprintf (name, sizeof (name), "clut%02d", i);
+ if (!gst_structure_get_int (structure, name, &value)) {
+ GST_ERROR_OBJECT (mux, "dvd-spu-clut-change event did not "
+ "contain %s field", name);
+ break;
+ }
+ clut[i] = value;
+ }
+
+ /* transform into private data for stream; text form */
+ gst_matroska_mux_build_vobsub_private (context, clut);
}
- break;
}
+ /* fall through */
default:
break;
}
- /* now GstCollectPads can take care of the rest, e.g. EOS */
- if (event)
- ret = mux->collect_event (pad, parent, event);
+ if (event != NULL)
+ return gst_collect_pads2_event_default (pads, data, event, FALSE);
return ret;
}
+static void
+gst_matroska_mux_set_codec_id (GstMatroskaTrackContext * context,
+ const char *id)
+{
+ g_assert (context && id);
+ if (context->codec_id)
+ g_free (context->codec_id);
+ context->codec_id = g_strdup (id);
+}
/**
* gst_matroska_mux_video_pad_setcaps:
/* find type */
if (!strcmp (mimetype, "video/x-raw")) {
const gchar *fstr;
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED);
fstr = gst_structure_get_string (structure, "format");
if (fstr && strlen (fstr) == 4)
videocontext->fourcc = GST_STR_FOURCC (fstr);
} else if (!strcmp (mimetype, "image/jpeg")) {
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
} else if (!strcmp (mimetype, "video/x-xvid") /* MS/VfW compatibility cases */
||!strcmp (mimetype, "video/x-huffyuv")
|| !strcmp (mimetype, "video/x-divx")
(guint8 *) bih + sizeof (gst_riff_strf_vids), -1);
}
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
+ gst_matroska_mux_free_codec_priv (context);
context->codec_priv = (gpointer) bih;
context->codec_priv_size = size;
} else if (!strcmp (mimetype, "video/x-h264")) {
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC);
-
- if (context->codec_priv != NULL) {
- g_free (context->codec_priv);
- context->codec_priv = NULL;
- context->codec_priv_size = 0;
- }
-
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC);
+ gst_matroska_mux_free_codec_priv (context);
/* Create avcC header */
if (codec_buf != NULL) {
context->codec_priv_size = gst_buffer_get_size (codec_buf);
} else if (!strcmp (mimetype, "video/x-theora")) {
const GValue *streamheader;
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_THEORA);
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_VIDEO_THEORA);
- if (context->codec_priv != NULL) {
- g_free (context->codec_priv);
- context->codec_priv = NULL;
- context->codec_priv_size = 0;
- }
+ gst_matroska_mux_free_codec_priv (context);
streamheader = gst_structure_get_value (structure, "streamheader");
if (!theora_streamheader_to_codecdata (streamheader, context)) {
goto refuse_caps;
}
} else if (!strcmp (mimetype, "video/x-dirac")) {
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_DIRAC);
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_VIDEO_DIRAC);
} else if (!strcmp (mimetype, "video/x-vp8")) {
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VP8);
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_VIDEO_VP8);
} else if (!strcmp (mimetype, "video/mpeg")) {
gint mpegversion;
gst_structure_get_int (structure, "mpegversion", &mpegversion);
switch (mpegversion) {
case 1:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG1);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_VIDEO_MPEG1);
break;
case 2:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG2);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_VIDEO_MPEG2);
break;
case 4:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
break;
default:
goto refuse_caps;
/* global headers may be in codec data */
if (codec_buf != NULL) {
+ gst_matroska_mux_free_codec_priv (context);
context->codec_priv_size = gst_buffer_get_size (codec_buf);
context->codec_priv = g_malloc0 (context->codec_priv_size);
gst_buffer_extract (codec_buf, 0, context->codec_priv, -1);
gst_structure_get_int (structure, "rmversion", &rmversion);
switch (rmversion) {
case 1:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1);
break;
case 2:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2);
break;
case 3:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3);
break;
case 4:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4);
break;
default:
goto refuse_caps;
gst_buffer_extract (codec_data_buf, 0, priv_data, -1);
+ gst_matroska_mux_free_codec_priv (context);
context->codec_priv = priv_data;
context->codec_priv_size = priv_data_size;
}
offset += gst_buffer_get_size (buf[i]);
}
+ gst_matroska_mux_free_codec_priv (context);
context->codec_priv = priv_data;
context->codec_priv_size = priv_data_size;
} 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)
return FALSE;
}
+ gst_matroska_mux_free_codec_priv (context);
context->codec_priv_size = gst_buffer_get_size (buffer) - 9;
context->codec_priv = g_malloc (context->codec_priv_size);
gst_buffer_extract (buffer, 9, context->codec_priv, -1);
bufval = &g_array_index (bufarr, GValue, i);
if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
- g_free (context->codec_priv);
- context->codec_priv = NULL;
- context->codec_priv_size = 0;
+ gst_matroska_mux_free_codec_priv (context);
GST_WARNING ("streamheaders array does not contain GstBuffers");
return FALSE;
}
return FALSE;
}
+ gst_matroska_mux_free_codec_priv (context);
context->codec_priv_size = gst_buffer_get_size (buffer);
context->codec_priv = g_malloc (context->codec_priv_size);
gst_buffer_extract (buffer, 0, context->codec_priv, -1);
bufval = &g_array_index (bufarr, GValue, 1);
if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
- g_free (context->codec_priv);
- context->codec_priv = NULL;
- context->codec_priv_size = 0;
+ gst_matroska_mux_free_codec_priv (context);
GST_WARNING ("streamheaders array does not contain GstBuffers");
return FALSE;
}
switch (layer) {
case 1:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1);
break;
case 2:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2);
break;
case 3:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3);
break;
default:
goto refuse_caps;
goto refuse_caps;
}
if (GST_AUDIO_INFO_IS_BIG_ENDIAN (&info))
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE);
else
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE);
break;
-
case GST_AUDIO_FORMAT_F32LE:
case GST_AUDIO_FORMAT_F64LE:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT);
break;
default:
}
audiocontext->bitdepth = GST_AUDIO_INFO_WIDTH (&info);
-
} else if (!strcmp (mimetype, "audio/x-vorbis")) {
const GValue *streamheader;
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_VORBIS);
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_VORBIS);
- if (context->codec_priv != NULL) {
- g_free (context->codec_priv);
- context->codec_priv = NULL;
- context->codec_priv_size = 0;
- }
+ gst_matroska_mux_free_codec_priv (context);
streamheader = gst_structure_get_value (structure, "streamheader");
if (!vorbis_streamheader_to_codecdata (streamheader, context)) {
} else if (!strcmp (mimetype, "audio/x-flac")) {
const GValue *streamheader;
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_FLAC);
- if (context->codec_priv != NULL) {
- g_free (context->codec_priv);
- context->codec_priv = NULL;
- context->codec_priv_size = 0;
- }
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_FLAC);
+
+ gst_matroska_mux_free_codec_priv (context);
streamheader = gst_structure_get_value (structure, "streamheader");
if (!flac_streamheader_to_codecdata (streamheader, context)) {
} else if (!strcmp (mimetype, "audio/x-speex")) {
const GValue *streamheader;
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_SPEEX);
- if (context->codec_priv != NULL) {
- g_free (context->codec_priv);
- context->codec_priv = NULL;
- context->codec_priv_size = 0;
- }
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_SPEEX);
+ gst_matroska_mux_free_codec_priv (context);
streamheader = gst_structure_get_value (structure, "streamheader");
if (!speex_streamheader_to_codecdata (streamheader, context)) {
goto refuse_caps;
}
} else if (!strcmp (mimetype, "audio/x-ac3")) {
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3);
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_AC3);
} else if (!strcmp (mimetype, "audio/x-eac3")) {
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_EAC3);
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_EAC3);
} else if (!strcmp (mimetype, "audio/x-dts")) {
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_DTS);
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_DTS);
} else if (!strcmp (mimetype, "audio/x-tta")) {
gint width;
gst_structure_get_int (structure, "width", &width);
audiocontext->bitdepth = width;
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_TTA);
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_TTA);
} else if (!strcmp (mimetype, "audio/x-pn-realaudio")) {
gint raversion;
gst_structure_get_int (structure, "raversion", &raversion);
switch (raversion) {
case 1:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4);
break;
case 2:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_28_8);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_AUDIO_REAL_28_8);
break;
case 8:
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK);
break;
default:
goto refuse_caps;
gst_buffer_extract (codec_data_buf, 0, priv_data, -1);
+ gst_matroska_mux_free_codec_priv (context);
+
context->codec_priv = priv_data;
context->codec_priv_size = priv_data_size;
}
(guint8 *) codec_priv + WAVEFORMATEX_SIZE, -1);
}
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_ACM);
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_ACM);
+ gst_matroska_mux_free_codec_priv (context);
context->codec_priv = (gpointer) codec_priv;
context->codec_priv_size = codec_priv_size;
}
}
}
+/* we probably don't have the data at start,
+ * so have to reserve (a maximum) space to write this at the end.
+ * bit spacy, but some formats can hold quite some */
+#define SUBTITLE_MAX_CODEC_PRIVATE 2048 /* must be > 128 */
/**
* gst_matroska_mux_subtitle_pad_setcaps:
static gboolean
gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps)
{
- /* FIXME:
- * Consider this as boilerplate code for now. There is
- * no single subtitle creation element in GStreamer,
- * neither do I know how subtitling works at all. */
-
/* There is now (at least) one such alement (kateenc), and I'm going
to handle it here and claim it works when it can be piped back
through GStreamer and VLC */
GstMatroskaPad *collect_pad;
const gchar *mimetype;
GstStructure *structure;
+ const GValue *value = NULL;
+ GstBuffer *buf = NULL;
+ gboolean ret = TRUE;
mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
scontext->invalid_utf8 = 0;
context->default_duration = 0;
- /* TODO: - other format than Kate */
-
if (!strcmp (mimetype, "subtitle/x-kate")) {
const GValue *streamheader;
- context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_SUBTITLE_KATE);
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_SUBTITLE_KATE);
- if (context->codec_priv != NULL) {
- g_free (context->codec_priv);
- context->codec_priv = NULL;
- context->codec_priv_size = 0;
- }
+ gst_matroska_mux_free_codec_priv (context);
streamheader = gst_structure_get_value (structure, "streamheader");
if (!kate_streamheader_to_codecdata (streamheader, context)) {
GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
("kate stream headers missing or malformed"));
- return FALSE;
+ ret = FALSE;
+ goto exit;
+ }
+ } else if (!strcmp (mimetype, "text/plain")) {
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_SUBTITLE_UTF8);
+ } else if (!strcmp (mimetype, "application/x-ssa")) {
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_SUBTITLE_SSA);
+ } else if (!strcmp (mimetype, "application/x-ass")) {
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_SUBTITLE_ASS);
+ } else if (!strcmp (mimetype, "application/x-usf")) {
+ gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_SUBTITLE_USF);
+ } else if (!strcmp (mimetype, "video/x-dvd-subpicture")) {
+ gst_matroska_mux_set_codec_id (context,
+ GST_MATROSKA_CODEC_ID_SUBTITLE_VOBSUB);
+ } else {
+ ret = FALSE;
+ goto exit;
+ }
+
+ /* maybe some private data, e.g. vobsub */
+ value = gst_structure_get_value (structure, "codec_data");
+ if (value)
+ buf = gst_value_get_buffer (value);
+ if (buf != NULL) {
+ GstMapInfo map;
+ guint8 *priv_data = NULL;
+
+ gst_buffer_map (buf, &map, GST_MAP_READ);
+
+ 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, &map);
+ return TRUE;
}
- return TRUE;
+
+ gst_matroska_mux_free_codec_priv (context);
+
+ priv_data = g_malloc0 (map.size);
+ memcpy (priv_data, map.data, map.size);
+ context->codec_priv = priv_data;
+ context->codec_priv_size = map.size;
+ gst_buffer_unmap (buf, &map);
}
- return FALSE;
+ GST_DEBUG_OBJECT (pad, "codec_id %s, codec data size %" G_GSIZE_FORMAT,
+ GST_STR_NULL (context->codec_id), context->codec_priv_size);
+
+exit:
+
+ return ret;
}
GstMatroskaCapsFunc capsfunc = NULL;
GstMatroskaTrackContext *context = NULL;
gint pad_id;
+ gboolean locked = TRUE;
+ gchar *id = NULL;
if (templ == gst_element_class_get_pad_template (klass, "audio_%u")) {
/* don't mix named and unnamed pads, if the pad already exists we fail when
g_new0 (GstMatroskaTrackSubtitleContext, 1);
context->type = GST_MATROSKA_TRACK_TYPE_SUBTITLE;
context->name = g_strdup ("Subtitle");
+ /* setcaps may only provide proper one a lot later */
+ id = g_strdup ("S_SUB_UNKNOWN");
+ locked = FALSE;
} else {
GST_WARNING_OBJECT (mux, "This is not our template!");
return NULL;
gst_matroskamux_pad_init (newpad);
collect_pad = (GstMatroskaPad *)
- gst_collect_pads_add_pad (mux->collect, GST_PAD (newpad),
- sizeof (GstMatroskaPad),
- (GstCollectDataDestroyNotify) gst_matroska_pad_free);
+ gst_collect_pads2_add_pad_full (mux->collect, GST_PAD (newpad),
+ sizeof (GstMatroskamuxPad),
+ (GstCollectData2DestroyNotify) gst_matroska_pad_free, locked);
collect_pad->track = context;
gst_matroska_pad_reset (collect_pad, FALSE);
-
- /* FIXME: hacked way to override/extend the event function of
- * GstCollectPads; because it sets its own event function giving the
- * element no access to events.
- * TODO GstCollectPads should really give its 'users' a clean chance to
- * properly handle events that are not meant for collectpads itself.
- * Perhaps a callback or so, though rejected (?) in #340060.
- * This would allow (clean) transcoding of info from demuxer/streams
- * to another muxer */
- mux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
- gst_pad_set_event_function (GST_PAD (newpad),
- GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_sink_event));
+ collect_pad->track->codec_id = id;
collect_pad->capsfunc = capsfunc;
gst_pad_set_active (GST_PAD (newpad), TRUE);
mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
- GstCollectData *cdata = (GstCollectData *) walk->data;
+ GstCollectData2 *cdata = (GstCollectData2 *) walk->data;
GstMatroskaPad *collect_pad = (GstMatroskaPad *) cdata;
if (cdata->pad == pad) {
}
}
- gst_collect_pads_remove_pad (mux->collect, pad);
+ gst_collect_pads2_remove_pad (mux->collect, pad);
if (gst_element_remove_pad (element, pad))
mux->num_streams--;
}
context->language);
}
+ /* FIXME: until we have a nice way of getting the codecname
+ * out of the caps, I'm not going to enable this. Too much
+ * (useless, double, boring) work... */
+ /* TODO: Use value from tags if any */
+ /*gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CODECNAME,
+ context->codec_name); */
+ gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKNAME, context->name);
+
/* type-specific stuff */
switch (context->type) {
case GST_MATROSKA_TRACK_TYPE_VIDEO:{
break;
}
+ /* this is what we write for now and must be filled
+ * and remainder void'ed later on */
+#define SUBTITLE_DUMMY_SIZE (1 + 1 + 14 + 1 + 2 + SUBTITLE_MAX_CODEC_PRIVATE)
+
+ case GST_MATROSKA_TRACK_TYPE_SUBTITLE:{
+ gpointer buf;
+
+ context->pos = ebml->pos;
+ /* CodecID is mandatory ... */
+ gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CODECID, "S_SUB_UNKNOWN");
+ /* reserve space */
+ buf = g_malloc0 (SUBTITLE_MAX_CODEC_PRIVATE);
+ gst_ebml_write_binary (ebml, GST_EBML_ID_VOID, buf,
+ SUBTITLE_MAX_CODEC_PRIVATE);
+ g_free (buf);
+ /* real data has to be written at finish */
+ return;
+ }
default:
/* doesn't need type-specific data */
break;
if (context->codec_priv)
gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECPRIVATE,
context->codec_priv, context->codec_priv_size);
- /* FIXME: until we have a nice way of getting the codecname
- * out of the caps, I'm not going to enable this. Too much
- * (useless, double, boring) work... */
- /* TODO: Use value from tags if any */
- /*gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CODECNAME,
- context->codec_name); */
- gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKNAME, context->name);
}
+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 {
if (tags != NULL && !gst_tag_list_is_empty (tags)) {
guint64 master_tags, master_tag;
- GST_DEBUG ("Writing tags");
+ GST_DEBUG_OBJECT (mux, "Writing tags");
/* TODO: maybe limit via the TARGETS id by looking at the source pad */
mux->tags_pos = ebml->pos;
child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKENTRY);
gst_matroska_mux_track_header (mux, collect_pad->track);
gst_ebml_write_master_finish (ebml, child);
- /* some remaing pad/track setup */
+ /* some remaining pad/track setup */
collect_pad->default_duration_scaled =
gst_util_uint64_scale (collect_pad->track->default_duration,
1, mux->time_scale);
}
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 ("Writing tags");
+ 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);
}
- /* update duration */
- /* first get the overall duration */
- /* a released track may have left a duration in here */
+ 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)
+ * - write some track header data for subtitles
+ */
duration = mux->duration;
+ pos = ebml->pos;
for (collected = mux->collect->data; collected;
collected = g_slist_next (collected)) {
GstMatroskaPad *collect_pad;
GstClockTime min_duration; /* observed minimum duration */
+ GstMatroskaTrackContext *context;
+ gint voidleft = 0, fill = 0;
+ gpointer codec_id;
collect_pad = (GstMatroskaPad *) collected->data;
+ context = collect_pad->track;
GST_DEBUG_OBJECT (mux,
"Pad %" GST_PTR_FORMAT " start ts %" GST_TIME_FORMAT
if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
duration < collect_pad->duration)
duration = collect_pad->duration;
- }
+
+ if (context->type != GST_MATROSKA_TRACK_TYPE_SUBTITLE || !context->pos)
+ continue;
+
+ again:
+ /* write subtitle type and possible private data */
+ gst_ebml_write_seek (ebml, context->pos);
+ /* complex way to write ascii to account for extra filling */
+ codec_id = g_malloc0 (strlen (context->codec_id) + 1 + fill);
+ strcpy (codec_id, context->codec_id);
+ gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECID,
+ codec_id, strlen (context->codec_id) + 1 + fill);
+ g_free (codec_id);
+ if (context->codec_priv)
+ gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECPRIVATE,
+ context->codec_priv, context->codec_priv_size);
+ voidleft = SUBTITLE_DUMMY_SIZE - (ebml->pos - context->pos);
+ /* void'ify; sigh, variable sized length field */
+ if (voidleft == 1) {
+ fill = 1;
+ goto again;
+ } else if (voidleft && voidleft <= 128)
+ gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, voidleft - 2);
+ else if (voidleft >= 130)
+ gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, voidleft - 3);
+ else if (voidleft == 129) {
+ gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 64);
+ gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 63);
+ }
+ }
+
+ /* seek back (optional, but do anyway) */
+ gst_ebml_write_seek (ebml, pos);
+
+ /* update duration */
if (duration != 0) {
GST_DEBUG_OBJECT (mux, "final total duration: %" GST_TIME_FORMAT,
GST_TIME_ARGS (duration));
gst_ebml_write_master_finish (ebml, mux->segment_pos);
}
-
-/**
- * gst_matroska_mux_best_pad:
- * @mux: #GstMatroskaMux
- * @popped: True if at least one buffer was popped from #GstCollectPads
- *
- * Find a pad with the oldest data
- * (data from this pad should be written first).
- *
- * Returns: Selected pad.
- */
-static GstMatroskaPad *
-gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped)
-{
- GSList *collected;
- GstMatroskaPad *best = NULL;
-
- *popped = FALSE;
- for (collected = mux->collect->data; collected;
- collected = g_slist_next (collected)) {
- GstMatroskaPad *collect_pad;
-
- collect_pad = (GstMatroskaPad *) collected->data;
- /* fetch a new buffer if needed */
- if (collect_pad->buffer == NULL) {
- collect_pad->buffer = gst_collect_pads_pop (mux->collect,
- (GstCollectData *) collect_pad);
-
- if (collect_pad->buffer != NULL) {
- GstClockTime time;
-
- *popped = TRUE;
- /* convert to running time */
- time = GST_BUFFER_TIMESTAMP (collect_pad->buffer);
- /* invalid should pass */
- if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (time))) {
- time = gst_segment_to_running_time (&collect_pad->collect.segment,
- GST_FORMAT_TIME, time);
- if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (time))) {
- GST_DEBUG_OBJECT (mux, "clipping buffer on pad %s outside segment",
- GST_PAD_NAME (collect_pad->collect.pad));
- gst_buffer_unref (collect_pad->buffer);
- collect_pad->buffer = NULL;
- return NULL;
- } else {
- GST_LOG_OBJECT (mux, "buffer ts %" GST_TIME_FORMAT " -> %"
- GST_TIME_FORMAT " running time",
- GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (collect_pad->buffer)),
- GST_TIME_ARGS (time));
- collect_pad->buffer =
- gst_buffer_make_writable (collect_pad->buffer);
- GST_BUFFER_TIMESTAMP (collect_pad->buffer) = time;
- }
- }
- }
- }
-
- /* if we have a buffer check if it is better then the current best one */
- if (collect_pad->buffer != NULL) {
- if (best == NULL || !GST_BUFFER_TIMESTAMP_IS_VALID (collect_pad->buffer)
- || (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)
- && GST_BUFFER_TIMESTAMP (collect_pad->buffer) <
- GST_BUFFER_TIMESTAMP (best->buffer))) {
- best = collect_pad;
- }
- }
- }
-
- return best;
-}
-
/**
* gst_matroska_mux_buffer_header:
* @track: Track context.
{
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);
* Returns: Result of the gst_pad_push issued to write the data.
*/
static GstFlowReturn
-gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
+gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad,
+ GstBuffer * buf)
{
GstEbmlWrite *ebml = mux->ebml_write;
- GstBuffer *buf, *hdr;
+ GstBuffer *hdr;
guint64 blockgroup;
gboolean write_duration;
gint16 relative_timestamp;
GstMatroskamuxPad *pad;
/* write data */
- buf = collect_pad->buffer;
- collect_pad->buffer = NULL;
pad = GST_MATROSKAMUX_PAD_CAST (collect_pad->collect.pad);
/* vorbis/theora headers are retrieved from caps and put in CodecPrivate */
}
}
-
/**
- * gst_matroska_mux_collected:
- * @pads: #GstCollectPads
+ * gst_matroska_mux_handle_buffer:
+ * @pads: #GstCollectPads2
* @uuser_data: #GstMatroskaMux
*
* Collectpads callback.
* Returns: #GstFlowReturn
*/
static GstFlowReturn
-gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data)
+gst_matroska_mux_handle_buffer (GstCollectPads2 * pads, GstCollectData2 * data,
+ GstBuffer * buf, gpointer user_data)
{
GstMatroskaMux *mux = GST_MATROSKA_MUX (user_data);
GstEbmlWrite *ebml = mux->ebml_write;
GstMatroskaPad *best;
- gboolean popped;
GstFlowReturn ret = GST_FLOW_OK;
GST_DEBUG_OBJECT (mux, "Collected pads");
mux->state = GST_MATROSKA_MUX_STATE_DATA;
}
- do {
- /* which stream to write from? */
- best = gst_matroska_mux_best_pad (mux, &popped);
+ /* provided with stream to write from */
+ best = (GstMatroskaPad *) data;
- /* if there is no best pad, we have reached EOS */
- if (best == NULL) {
- /* buffer popped, but none returned means it was clipped */
- if (popped)
- break;
- GST_DEBUG_OBJECT (mux, "No best pad finishing...");
- if (!mux->streamable) {
- gst_matroska_mux_finish (mux);
- } else {
- GST_DEBUG_OBJECT (mux, "... but streamable, nothing to finish");
- }
- gst_pad_push_event (mux->srcpad, gst_event_new_eos ());
- ret = GST_FLOW_UNEXPECTED;
- break;
+ /* if there is no best pad, we have reached EOS */
+ if (best == NULL) {
+ GST_DEBUG_OBJECT (mux, "No best pad finishing...");
+ if (!mux->streamable) {
+ gst_matroska_mux_finish (mux);
+ } else {
+ GST_DEBUG_OBJECT (mux, "... but streamable, nothing to finish");
}
- GST_DEBUG_OBJECT (best->collect.pad, "best pad - buffer ts %"
- GST_TIME_FORMAT " dur %" GST_TIME_FORMAT,
- GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (best->buffer)),
- GST_TIME_ARGS (GST_BUFFER_DURATION (best->buffer)));
+ gst_pad_push_event (mux->srcpad, gst_event_new_eos ());
+ ret = GST_FLOW_EOS;
+ goto exit;
+ }
- /* make note of first and last encountered timestamps, so we can calculate
- * the actual duration later when we send an updated header on eos */
- if (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)) {
- GstClockTime start_ts = GST_BUFFER_TIMESTAMP (best->buffer);
- GstClockTime end_ts = start_ts;
+ /* if we have a best stream, should also have a buffer */
+ g_assert (buf);
- if (GST_BUFFER_DURATION_IS_VALID (best->buffer))
- end_ts += GST_BUFFER_DURATION (best->buffer);
- else if (best->track->default_duration)
- end_ts += best->track->default_duration;
+ GST_DEBUG_OBJECT (best->collect.pad, "best pad - buffer ts %"
+ GST_TIME_FORMAT " dur %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
+ GST_TIME_ARGS (GST_BUFFER_DURATION (buf)));
- if (!GST_CLOCK_TIME_IS_VALID (best->end_ts) || end_ts > best->end_ts)
- best->end_ts = end_ts;
+ /* make note of first and last encountered timestamps, so we can calculate
+ * the actual duration later when we send an updated header on eos */
+ if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
+ GstClockTime start_ts = GST_BUFFER_TIMESTAMP (buf);
+ GstClockTime end_ts = start_ts;
- if (G_UNLIKELY (best->start_ts == GST_CLOCK_TIME_NONE ||
- start_ts < best->start_ts))
- best->start_ts = start_ts;
- }
+ if (GST_BUFFER_DURATION_IS_VALID (buf))
+ end_ts += GST_BUFFER_DURATION (buf);
+ else if (best->track->default_duration)
+ end_ts += best->track->default_duration;
+
+ if (!GST_CLOCK_TIME_IS_VALID (best->end_ts) || end_ts > best->end_ts)
+ best->end_ts = end_ts;
+
+ if (G_UNLIKELY (best->start_ts == GST_CLOCK_TIME_NONE ||
+ start_ts < best->start_ts))
+ best->start_ts = start_ts;
+ }
- /* write one buffer */
- ret = gst_matroska_mux_write_data (mux, best);
- } while (ret == GST_FLOW_OK && !popped);
+ /* write one buffer */
+ ret = gst_matroska_mux_write_data (mux, best, buf);
+exit:
return ret;
}
case GST_STATE_CHANGE_NULL_TO_READY:
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
- gst_collect_pads_start (mux->collect);
+ gst_collect_pads2_start (mux->collect);
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
- gst_collect_pads_stop (mux->collect);
+ gst_collect_pads2_stop (mux->collect);
break;
default:
break;