X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gst%2Fmatroska%2Fmatroska-mux.c;h=4cb87da7f029e85b6cc2248e8e9a0c279b4f1403;hb=04b4d30f2cf7f8196703e14647cbb88b7ec1ad37;hp=55fd051aaa544f587373aacf8187eeaca6972304;hpb=d895ac645f017f4d048b0fcbde44b3ef6c9fb4f3;p=platform%2Fupstream%2Fgst-plugins-good.git diff --git a/gst/matroska/matroska-mux.c b/gst/matroska/matroska-mux.c index 55fd051..4cb87da 100644 --- a/gst/matroska/matroska-mux.c +++ b/gst/matroska/matroska-mux.c @@ -2,6 +2,7 @@ * (c) 2003 Ronald Bultje * (c) 2005 Michal Benes * (c) 2008 Sebastian Dröge + * (c) 2011 Mark Nauwelaerts * * matroska-mux.c: matroska file/stream muxer * @@ -56,6 +57,8 @@ #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 @@ -147,10 +150,10 @@ static GstStaticPadTemplate audiosink_templ = 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 "; " @@ -166,6 +169,7 @@ static GstStaticPadTemplate audiosink_templ = 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 }, " @@ -182,24 +186,32 @@ static GstStaticPadTemplate audiosink_templ = ); 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, @@ -255,7 +267,7 @@ gst_matroska_mux_class_init (GstMatroskaMuxClass * klass) 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 "); @@ -414,10 +426,13 @@ gst_matroska_mux_init (GstMatroskaMux * mux) 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; @@ -534,12 +549,6 @@ gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full) 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; @@ -567,7 +576,6 @@ gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full) /* 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; @@ -634,6 +642,13 @@ gst_matroska_mux_reset (GstElement * element) /* 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; } /** @@ -664,6 +679,55 @@ gst_matroska_mux_handle_src_event (GstPad * pad, GstObject * parent, 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. @@ -674,15 +738,22 @@ gst_matroska_mux_handle_src_event (GstPad * pad, GstObject * parent, * 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; @@ -701,11 +772,6 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstObject * parent, 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; @@ -727,17 +793,31 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstObject * parent, 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:{ @@ -748,20 +828,53 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstObject * parent, 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: @@ -871,12 +984,13 @@ skip_details: /* 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") @@ -969,18 +1083,15 @@ skip_details: (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); @@ -990,13 +1101,9 @@ skip_details: } 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)) { @@ -1005,22 +1112,25 @@ skip_details: 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; @@ -1028,6 +1138,7 @@ skip_details: /* 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); @@ -1043,16 +1154,20 @@ skip_details: 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; @@ -1070,6 +1185,7 @@ skip_details: 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; } @@ -1153,6 +1269,7 @@ xiphN_streamheader_to_codecdata (const GValue * streamheader, 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; @@ -1201,14 +1318,15 @@ vorbis_streamheader_to_codecdata (const GValue * streamheader, } 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); } } @@ -1234,10 +1352,11 @@ theora_streamheader_to_codecdata (const GValue * streamheader, } 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; @@ -1269,7 +1388,7 @@ theora_streamheader_to_codecdata (const GValue * streamheader, } hdr += 3 + 3; - gst_buffer_unmap (buf0, data, -1); + gst_buffer_unmap (buf0, &map); } if (buf0) @@ -1337,6 +1456,7 @@ flac_streamheader_to_codecdata (const GValue * streamheader, 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); @@ -1346,9 +1466,7 @@ flac_streamheader_to_codecdata (const GValue * streamheader, 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; } @@ -1403,6 +1521,7 @@ speex_streamheader_to_codecdata (const GValue * streamheader, 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); @@ -1410,9 +1529,7 @@ speex_streamheader_to_codecdata (const GValue * streamheader, 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; } @@ -1550,13 +1667,16 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) 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; @@ -1619,14 +1739,16 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * 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: @@ -1635,17 +1757,12 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) } 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)) { @@ -1656,12 +1773,9 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) } 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)) { @@ -1672,12 +1786,8 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) } 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)) { @@ -1686,11 +1796,11 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) 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; @@ -1699,7 +1809,7 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) 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; @@ -1708,13 +1818,16 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) 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; @@ -1732,6 +1845,8 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * 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; } @@ -1815,7 +1930,8 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) (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; } @@ -1831,6 +1947,10 @@ refuse_caps: } } +/* 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: @@ -1844,11 +1964,6 @@ refuse_caps: 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 */ @@ -1859,6 +1974,9 @@ gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps) 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)); @@ -1878,29 +1996,71 @@ gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps) 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; } @@ -1927,6 +2087,8 @@ gst_matroska_mux_request_new_pad (GstElement * element, 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 @@ -1970,6 +2132,9 @@ gst_matroska_mux_request_new_pad (GstElement * element, 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; @@ -1981,24 +2146,13 @@ gst_matroska_mux_request_new_pad (GstElement * element, 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); @@ -2036,7 +2190,7 @@ gst_matroska_mux_release_pad (GstElement * element, GstPad * pad) 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) { @@ -2057,7 +2211,7 @@ gst_matroska_mux_release_pad (GstElement * element, GstPad * 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--; } @@ -2094,6 +2248,14 @@ gst_matroska_mux_track_header (GstMatroskaMux * mux, 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:{ @@ -2144,6 +2306,24 @@ gst_matroska_mux_track_header (GstMatroskaMux * mux, 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; @@ -2153,15 +2333,112 @@ gst_matroska_mux_track_header (GstMatroskaMux * mux, 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: @@ -2176,6 +2453,7 @@ gst_matroska_mux_start (GstMatroskaMux * mux) 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 @@ -2188,6 +2466,30 @@ gst_matroska_mux_start (GstMatroskaMux * mux) 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 { @@ -2229,7 +2531,7 @@ gst_matroska_mux_start (GstMatroskaMux * mux) 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; @@ -2303,7 +2605,7 @@ gst_matroska_mux_start (GstMatroskaMux * mux) 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); @@ -2311,6 +2613,68 @@ gst_matroska_mux_start (GstMatroskaMux * mux) } 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); } @@ -2374,6 +2738,44 @@ gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag, } } +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: @@ -2427,22 +2829,45 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) /* 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, @@ -2455,9 +2880,10 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) 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; @@ -2466,9 +2892,9 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) 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; @@ -2478,16 +2904,35 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) 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 @@ -2509,7 +2954,41 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) 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)); @@ -2532,77 +3011,6 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) 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. @@ -2642,18 +3050,20 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux, { 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; } @@ -2661,7 +3071,7 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux, /* 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; } @@ -2688,11 +3098,11 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux, } 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); @@ -2727,7 +3137,7 @@ gst_matroska_mux_stop_streamheader (GstMatroskaMux * mux) 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); @@ -2748,10 +3158,11 @@ gst_matroska_mux_stop_streamheader (GstMatroskaMux * mux) * 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; @@ -2761,8 +3172,6 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) 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 */ @@ -2952,10 +3361,9 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) } } - /** - * gst_matroska_mux_collected: - * @pads: #GstCollectPads + * gst_matroska_mux_handle_buffer: + * @pads: #GstCollectPads2 * @uuser_data: #GstMatroskaMux * * Collectpads callback. @@ -2963,12 +3371,12 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) * 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"); @@ -2987,53 +3395,53 @@ gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data) 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; } @@ -3057,12 +3465,12 @@ gst_matroska_mux_change_state (GstElement * element, GstStateChange transition) 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;