X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gst%2Fmatroska%2Fmatroska-mux.c;h=d48d56c18eba003dd360d9c3fb5ebc040edaf9f3;hb=a95acb7122248ef22bf251cb053416a3b63ff806;hp=421dcd50c52af6d28ac1e235f60703b05b4e61fa;hpb=19b7001bf99ea37a91af6061ca98e50b08064017;p=platform%2Fupstream%2Fgst-plugins-good.git diff --git a/gst/matroska/matroska-mux.c b/gst/matroska/matroska-mux.c index 421dcd5..d48d56c 100644 --- a/gst/matroska/matroska-mux.c +++ b/gst/matroska/matroska-mux.c @@ -46,8 +46,12 @@ #endif #include +#include #include +#include +#include + #include "matroska-mux.h" #include "matroska-ids.h" @@ -58,11 +62,18 @@ enum { ARG_0, ARG_WRITING_APP, - ARG_MATROSKA_VERSION + ARG_DOCTYPE_VERSION, + ARG_MIN_INDEX_INTERVAL, + ARG_STREAMABLE }; -#define DEFAULT_MATROSKA_VERSION 1 +#define DEFAULT_DOCTYPE_VERSION 2 #define DEFAULT_WRITING_APP "GStreamer Matroska muxer" +#define DEFAULT_MIN_INDEX_INTERVAL 0 +#define DEFAULT_STREAMABLE FALSE + +/* WAVEFORMATEX is gst_riff_strf_auds + an extra guint16 extension size */ +#define WAVEFORMATEX_SIZE (2 + sizeof (gst_riff_strf_auds)) static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, @@ -79,19 +90,19 @@ static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src", "width = (int) [ 16, 4096 ], " \ "height = (int) [ 16, 4096 ] " -/* FIXME: +/* FIXME: * * require codec data, etc as needed */ static GstStaticPadTemplate videosink_templ = - GST_STATIC_PAD_TEMPLATE ("video_%d", + GST_STATIC_PAD_TEMPLATE ("video_%u", GST_PAD_SINK, GST_PAD_REQUEST, GST_STATIC_CAPS ("video/mpeg, " "mpegversion = (int) { 1, 2, 4 }, " "systemstream = (boolean) false, " COMMON_VIDEO_CAPS "; " - "video/x-h264, " + "video/x-h264, stream-format=avc, alignment=au, " COMMON_VIDEO_CAPS "; " "video/x-divx, " COMMON_VIDEO_CAPS "; " @@ -113,9 +124,12 @@ static GstStaticPadTemplate videosink_templ = "video/x-pn-realvideo, " "rmversion = (int) [1, 4], " COMMON_VIDEO_CAPS "; " + "video/x-vp8, " + COMMON_VIDEO_CAPS "; " "video/x-raw-yuv, " "format = (fourcc) { YUY2, I420, YV12, UYVY, AYUV }, " - COMMON_VIDEO_CAPS) + COMMON_VIDEO_CAPS "; " + "video/x-wmv, " "wmvversion = (int) [ 1, 3 ], " COMMON_VIDEO_CAPS) ); #define COMMON_AUDIO_CAPS \ @@ -126,18 +140,23 @@ static GstStaticPadTemplate videosink_templ = * * require codec data, etc as needed */ static GstStaticPadTemplate audiosink_templ = - GST_STATIC_PAD_TEMPLATE ("audio_%d", + GST_STATIC_PAD_TEMPLATE ("audio_%u", GST_PAD_SINK, GST_PAD_REQUEST, 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 }, " COMMON_AUDIO_CAPS "; " "audio/x-ac3, " COMMON_AUDIO_CAPS "; " + "audio/x-eac3, " + COMMON_AUDIO_CAPS "; " + "audio/x-dts, " + COMMON_AUDIO_CAPS "; " "audio/x-vorbis, " COMMON_AUDIO_CAPS "; " "audio/x-flac, " @@ -175,14 +194,21 @@ static GstStaticPadTemplate audiosink_templ = "width = (int) { 8, 16, 24 }, " "channels = (int) { 1, 2 }, " "rate = (int) [ 8000, 96000 ]; " "audio/x-pn-realaudio, " - "raversion = (int) { 1, 2, 8 }, " COMMON_AUDIO_CAPS ";") + "raversion = (int) { 1, 2, 8 }, " COMMON_AUDIO_CAPS "; " + "audio/x-wma, " "wmaversion = (int) [ 1, 3 ], " + "block_align = (int) [ 0, 65535 ], bitrate = (int) [ 0, 524288 ], " + COMMON_AUDIO_CAPS ";" + "audio/x-alaw, " + "channels = (int) {1, 2}, " "rate = (int) [ 8000, 192000 ]; " + "audio/x-mulaw, " + "channels = (int) {1, 2}, " "rate = (int) [ 8000, 192000 ]") ); static GstStaticPadTemplate subtitlesink_templ = -GST_STATIC_PAD_TEMPLATE ("subtitle_%d", +GST_STATIC_PAD_TEMPLATE ("subtitle_%u", GST_PAD_SINK, GST_PAD_REQUEST, - GST_STATIC_CAPS_ANY); + GST_STATIC_CAPS ("subtitle/x-kate")); static GArray *used_uids; G_LOCK_DEFINE_STATIC (used_uids); @@ -232,6 +258,9 @@ static gboolean kate_streamheader_to_codecdata (const GValue * streamheader, GstMatroskaTrackContext * context); static gboolean flac_streamheader_to_codecdata (const GValue * streamheader, GstMatroskaTrackContext * context); +static void +gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag, + gpointer data); static void gst_matroska_mux_add_interfaces (GType type) @@ -244,23 +273,6 @@ gst_matroska_mux_add_interfaces (GType type) static void gst_matroska_mux_base_init (gpointer g_class) { - GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); - - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&videosink_templ)); - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&audiosink_templ)); - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&subtitlesink_templ)); - gst_element_class_add_pad_template (element_class, - gst_static_pad_template_get (&src_templ)); - gst_element_class_set_details_simple (element_class, "Matroska muxer", - "Codec/Muxer", - "Muxes video/audio/subtitle streams into a matroska stream", - "Ronald Bultje "); - - GST_DEBUG_CATEGORY_INIT (matroskamux_debug, "matroskamux", 0, - "Matroska muxer"); } static void @@ -272,6 +284,22 @@ gst_matroska_mux_class_init (GstMatroskaMuxClass * klass) gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&videosink_templ)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&audiosink_templ)); + gst_element_class_add_pad_template (gstelement_class, + 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", + "Codec/Muxer", + "Muxes video/audio/subtitle streams into a matroska stream", + "GStreamer maintainers "); + + GST_DEBUG_CATEGORY_INIT (matroskamux_debug, "matroskamux", 0, + "Matroska muxer"); + gobject_class->finalize = gst_matroska_mux_finalize; gobject_class->get_property = gst_matroska_mux_get_property; @@ -280,11 +308,23 @@ gst_matroska_mux_class_init (GstMatroskaMuxClass * klass) g_object_class_install_property (gobject_class, ARG_WRITING_APP, g_param_spec_string ("writing-app", "Writing application.", "The name the application that creates the matroska file.", - NULL, G_PARAM_READWRITE)); - g_object_class_install_property (gobject_class, ARG_MATROSKA_VERSION, - g_param_spec_int ("version", "Matroska version", - "This parameter determines what matroska features can be used.", - 1, 2, DEFAULT_MATROSKA_VERSION, G_PARAM_READWRITE)); + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_DOCTYPE_VERSION, + g_param_spec_int ("version", "DocType version", + "This parameter determines what Matroska features can be used.", + 1, 2, DEFAULT_DOCTYPE_VERSION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_MIN_INDEX_INTERVAL, + g_param_spec_int64 ("min-index-interval", "Minimum time between index " + "entries", "An index entry is created every so many nanoseconds.", + 0, G_MAXINT64, DEFAULT_MIN_INDEX_INTERVAL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_STREAMABLE, + g_param_spec_boolean ("streamable", "Determines whether output should " + "be streamable", "If set to true, the output should be as if it is " + "to be streamed and hence no indexes written or duration written.", + DEFAULT_STREAMABLE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_STATIC_STRINGS)); gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_matroska_mux_change_state); @@ -305,7 +345,12 @@ gst_matroska_mux_class_init (GstMatroskaMuxClass * klass) static void gst_matroska_mux_init (GstMatroskaMux * mux, GstMatroskaMuxClass * g_class) { - mux->srcpad = gst_pad_new_from_static_template (&src_templ, "src"); + GstPadTemplate *templ; + + templ = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src"); + mux->srcpad = gst_pad_new_from_template (templ, "src"); + gst_pad_set_event_function (mux->srcpad, gst_matroska_mux_handle_src_event); gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad); @@ -315,10 +360,13 @@ gst_matroska_mux_init (GstMatroskaMux * mux, GstMatroskaMuxClass * g_class) mux); mux->ebml_write = gst_ebml_write_new (mux->srcpad); + mux->doctype = GST_MATROSKA_DOCTYPE_MATROSKA; /* property defaults */ - mux->matroska_version = DEFAULT_MATROSKA_VERSION; + mux->doctype_version = DEFAULT_DOCTYPE_VERSION; mux->writing_app = g_strdup (DEFAULT_WRITING_APP); + mux->min_index_interval = DEFAULT_MIN_INDEX_INTERVAL; + mux->streamable = DEFAULT_STREAMABLE; /* initialize internal variables */ mux->index = NULL; @@ -343,6 +391,8 @@ gst_matroska_mux_finalize (GObject * object) { GstMatroskaMux *mux = GST_MATROSKA_MUX (object); + gst_event_replace (&mux->force_key_unit_event, NULL); + gst_object_unref (mux->collect); gst_object_unref (mux->ebml_write); if (mux->writing_app) @@ -449,7 +499,7 @@ gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full) break; default: g_assert_not_reached (); - break; + return; } context->type = type; @@ -513,12 +563,14 @@ gst_matroska_mux_reset (GstElement * element) /* reset timers */ mux->time_scale = GST_MSECOND; + mux->max_cluster_duration = G_MAXINT16 * mux->time_scale; mux->duration = 0; /* reset cluster */ mux->cluster = 0; mux->cluster_time = 0; mux->cluster_pos = 0; + mux->prev_cluster_size = 0; /* reset tags */ gst_tag_setter_reset_tags (GST_TAG_SETTER (mux)); @@ -529,7 +581,7 @@ gst_matroska_mux_reset (GstElement * element) * @pad: Pad which received the event. * @event: Received event. * - * handle events - copied from oggmux without understanding + * handle events - copied from oggmux without understanding * * Returns: #TRUE on success. */ @@ -572,7 +624,9 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event) mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad)); switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_TAG: + case GST_EVENT_TAG:{ + gchar *lang = NULL; + GST_DEBUG_OBJECT (mux, "received tag event"); gst_event_parse_tag (event, &list); @@ -580,26 +634,61 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event) g_assert (collect_pad); context = collect_pad->track; g_assert (context); - /* FIXME ? - * strictly speaking, the incoming language code may only be 639-1, so not - * 639-2 according to matroska specs, but it will have to do for now */ - gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &context->language); + /* 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; + + lang_code = gst_tag_get_language_code_iso_639_2B (lang); + if (lang_code) { + GST_INFO_OBJECT (pad, "Setting language to '%s'", lang_code); + context->language = g_strdup (lang_code); + } else { + GST_WARNING_OBJECT (pad, "Did not get language code for '%s'", lang); + } + g_free (lang); + } + + /* FIXME: what about stream-specific tags? */ gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), list, gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (mux))); - break; - case GST_EVENT_NEWSEGMENT: - /* We don't support NEWSEGMENT events */ - ret = FALSE; + gst_event_unref (event); + /* handled this, don't want collectpads to forward it downstream */ + event = NULL; + break; + } + case GST_EVENT_NEWSEGMENT:{ + GstFormat format; + + gst_event_parse_new_segment (event, NULL, NULL, &format, NULL, NULL, + NULL); + if (format != GST_FORMAT_TIME) { + ret = FALSE; + gst_event_unref (event); + event = NULL; + } break; + } + case GST_EVENT_CUSTOM_DOWNSTREAM:{ + const GstStructure *structure; + + structure = gst_event_get_structure (event); + if (gst_structure_has_name (structure, "GstForceKeyUnit")) { + gst_event_replace (&mux->force_key_unit_event, NULL); + mux->force_key_unit_event = event; + event = NULL; + } + break; + } default: break; } /* now GstCollectPads can take care of the rest, e.g. EOS */ - if (ret) + if (event) ret = mux->collect_event (pad, event); + gst_object_unref (mux); return ret; @@ -628,6 +717,7 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps) const GstBuffer *codec_buf = NULL; gint width, height, pixel_width, pixel_height; gint fps_d, fps_n; + gboolean interlaced = FALSE; mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad)); @@ -644,14 +734,21 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps) mimetype = gst_structure_get_name (structure); + if (gst_structure_get_boolean (structure, "interlaced", &interlaced) + && interlaced) + context->flags |= GST_MATROSKA_VIDEOTRACK_INTERLACED; + if (!strcmp (mimetype, "video/x-theora")) { /* we'll extract the details later from the theora identification header */ goto skip_details; } /* get general properties */ - gst_structure_get_int (structure, "width", &width); - gst_structure_get_int (structure, "height", &height); + /* spec says it is mandatory */ + if (!gst_structure_get_int (structure, "width", &width) || + !gst_structure_get_int (structure, "height", &height)) + goto refuse_caps; + videocontext->pixel_width = width; videocontext->pixel_height = height; if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d) @@ -699,20 +796,16 @@ skip_details: if (!strcmp (mimetype, "video/x-raw-yuv")) { context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED); gst_structure_get_fourcc (structure, "format", &videocontext->fourcc); - - return TRUE; - } else if (!strcmp (mimetype, "image/jpeg")) { - context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG); - - return TRUE; } else if (!strcmp (mimetype, "video/x-xvid") /* MS/VfW compatibility cases */ ||!strcmp (mimetype, "video/x-huffyuv") || !strcmp (mimetype, "video/x-divx") || !strcmp (mimetype, "video/x-dv") || !strcmp (mimetype, "video/x-h263") - || !strcmp (mimetype, "video/x-msmpeg")) { - BITMAPINFOHEADER *bih; - gint size = sizeof (BITMAPINFOHEADER); + || !strcmp (mimetype, "video/x-msmpeg") + || !strcmp (mimetype, "video/x-wmv") + || !strcmp (mimetype, "image/jpeg")) { + gst_riff_strf_vids *bih; + gint size = sizeof (gst_riff_strf_vids); guint32 fourcc = 0; if (!strcmp (mimetype, "video/x-xvid")) @@ -753,35 +846,49 @@ skip_details: goto msmpeg43; break; } + } else if (!strcmp (mimetype, "video/x-wmv")) { + gint wmvversion; + guint32 format; + if (gst_structure_get_fourcc (structure, "format", &format)) { + fourcc = format; + } else if (gst_structure_get_int (structure, "wmvversion", &wmvversion)) { + if (wmvversion == 2) { + fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '2'); + } else if (wmvversion == 1) { + fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '1'); + } else if (wmvversion == 3) { + fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '3'); + } + } + } else if (!strcmp (mimetype, "image/jpeg")) { + fourcc = GST_MAKE_FOURCC ('M', 'J', 'P', 'G'); } if (!fourcc) - return FALSE; - - bih = g_new0 (BITMAPINFOHEADER, 1); - GST_WRITE_UINT32_LE (&bih->bi_size, size); - GST_WRITE_UINT32_LE (&bih->bi_width, videocontext->pixel_width); - GST_WRITE_UINT32_LE (&bih->bi_height, videocontext->pixel_height); - GST_WRITE_UINT32_LE (&bih->bi_compression, fourcc); - GST_WRITE_UINT16_LE (&bih->bi_planes, (guint16) 1); - GST_WRITE_UINT16_LE (&bih->bi_bit_count, (guint16) 24); - GST_WRITE_UINT32_LE (&bih->bi_size_image, videocontext->pixel_width * + goto refuse_caps; + + bih = g_new0 (gst_riff_strf_vids, 1); + GST_WRITE_UINT32_LE (&bih->size, size); + GST_WRITE_UINT32_LE (&bih->width, videocontext->pixel_width); + GST_WRITE_UINT32_LE (&bih->height, videocontext->pixel_height); + GST_WRITE_UINT32_LE (&bih->compression, fourcc); + GST_WRITE_UINT16_LE (&bih->planes, (guint16) 1); + GST_WRITE_UINT16_LE (&bih->bit_cnt, (guint16) 24); + GST_WRITE_UINT32_LE (&bih->image_size, videocontext->pixel_width * videocontext->pixel_height * 3); /* process codec private/initialization data, if any */ if (codec_buf) { size += GST_BUFFER_SIZE (codec_buf); bih = g_realloc (bih, size); - GST_WRITE_UINT32_LE (&bih->bi_size, size); - memcpy ((guint8 *) bih + sizeof (BITMAPINFOHEADER), + GST_WRITE_UINT32_LE (&bih->size, size); + memcpy ((guint8 *) bih + sizeof (gst_riff_strf_vids), GST_BUFFER_DATA (codec_buf), GST_BUFFER_SIZE (codec_buf)); } context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC); context->codec_priv = (gpointer) bih; context->codec_priv_size = size; - - return TRUE; } else if (!strcmp (mimetype, "video/x-h264")) { context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC); @@ -798,8 +905,6 @@ skip_details: memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf), context->codec_priv_size); } - - return TRUE; } else if (!strcmp (mimetype, "video/x-theora")) { const GValue *streamheader; @@ -815,13 +920,12 @@ skip_details: if (!theora_streamheader_to_codecdata (streamheader, context)) { GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL), ("theora stream headers missing or malformed")); - return FALSE; + goto refuse_caps; } - return TRUE; } else if (!strcmp (mimetype, "video/x-dirac")) { context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_DIRAC); - - return TRUE; + } else if (!strcmp (mimetype, "video/x-vp8")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VP8); } else if (!strcmp (mimetype, "video/mpeg")) { gint mpegversion; @@ -837,7 +941,7 @@ skip_details: context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP); break; default: - return FALSE; + goto refuse_caps; } /* global headers may be in codec data */ @@ -847,14 +951,10 @@ skip_details: memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf), context->codec_priv_size); } - - return TRUE; } else if (!strcmp (mimetype, "video/x-msmpeg")) { msmpeg43: /* can only make it here if preceding case verified it was version 3 */ context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3); - - return TRUE; } else if (!strcmp (mimetype, "video/x-pn-realvideo")) { gint rmversion; const GValue *mdpr_data; @@ -874,7 +974,7 @@ skip_details: context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4); break; default: - return FALSE; + goto refuse_caps; } mdpr_data = gst_structure_get_value (structure, "mdpr_data"); @@ -892,11 +992,17 @@ skip_details: context->codec_priv = priv_data; context->codec_priv_size = priv_data_size; } - - return TRUE; } - return FALSE; + return TRUE; + + /* ERRORS */ +refuse_caps: + { + GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT, + GST_PAD_NAME (pad), caps); + return FALSE; + } } /* N > 0 to expect a particular number of headers, negative if the @@ -991,99 +1097,7 @@ wrong_type: } wrong_count: { - GST_WARNING ("got %u streamheaders, not 3 as expected", bufarr->len); - return FALSE; - } -wrong_content_type: - { - GST_WARNING ("streamheaders array does not contain GstBuffers"); - return FALSE; - } -} - -/* FIXME: after release make all code use xiph3_streamheader_to_codecdata() */ -static gboolean -xiph3_streamheader_to_codecdata (const GValue * streamheader, - GstMatroskaTrackContext * context, GstBuffer ** p_buf0) -{ - GstBuffer *buf[3]; - GArray *bufarr; - guint8 *priv_data; - guint i, offset, priv_data_size; - - if (streamheader == NULL) - goto no_stream_headers; - - if (G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) - goto wrong_type; - - bufarr = g_value_peek_pointer (streamheader); - if (bufarr->len != 3) - goto wrong_count; - - context->xiph_headers_to_skip = bufarr->len; - - for (i = 0; i < 3; i++) { - GValue *bufval = &g_array_index (bufarr, GValue, i); - - if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) - goto wrong_content_type; - - buf[i] = g_value_peek_pointer (bufval); - } - - priv_data_size = 1; - priv_data_size += GST_BUFFER_SIZE (buf[0]) / 0xff + 1; - priv_data_size += GST_BUFFER_SIZE (buf[1]) / 0xff + 1; - - for (i = 0; i < 3; ++i) { - priv_data_size += GST_BUFFER_SIZE (buf[i]); - } - - priv_data = g_malloc0 (priv_data_size); - - priv_data[0] = 2; - offset = 1; - - for (i = 0; i < GST_BUFFER_SIZE (buf[0]) / 0xff; ++i) { - priv_data[offset++] = 0xff; - } - priv_data[offset++] = GST_BUFFER_SIZE (buf[0]) % 0xff; - - for (i = 0; i < GST_BUFFER_SIZE (buf[1]) / 0xff; ++i) { - priv_data[offset++] = 0xff; - } - priv_data[offset++] = GST_BUFFER_SIZE (buf[1]) % 0xff; - - for (i = 0; i < 3; ++i) { - memcpy (priv_data + offset, GST_BUFFER_DATA (buf[i]), - GST_BUFFER_SIZE (buf[i])); - offset += GST_BUFFER_SIZE (buf[i]); - } - - context->codec_priv = priv_data; - context->codec_priv_size = priv_data_size; - - if (p_buf0) - *p_buf0 = gst_buffer_ref (buf[0]); - - return TRUE; - -/* ERRORS */ -no_stream_headers: - { - GST_WARNING ("required streamheaders missing in sink caps!"); - return FALSE; - } -wrong_type: - { - GST_WARNING ("streamheaders are not a GST_TYPE_ARRAY, but a %s", - G_VALUE_TYPE_NAME (streamheader)); - return FALSE; - } -wrong_count: - { - GST_WARNING ("got %u streamheaders, not 3 as expected", bufarr->len); + GST_WARNING ("got %u streamheaders, not %d as expected", bufarr->len, N); return FALSE; } wrong_content_type: @@ -1099,8 +1113,7 @@ vorbis_streamheader_to_codecdata (const GValue * streamheader, { GstBuffer *buf0 = NULL; - /* FIXME: change to use xiphN_streamheader_to_codecdata() after release */ - if (!xiph3_streamheader_to_codecdata (streamheader, context, &buf0)) + if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3)) return FALSE; if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 4) { @@ -1129,8 +1142,7 @@ theora_streamheader_to_codecdata (const GValue * streamheader, { GstBuffer *buf0 = NULL; - /* FIXME: change to use xiphN_streamheader_to_codecdata() after release */ - if (!xiph3_streamheader_to_codecdata (streamheader, context, &buf0)) + if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3)) return FALSE; if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 26) { @@ -1333,10 +1345,10 @@ speex_streamheader_to_codecdata (const GValue * streamheader, return TRUE; } -static gchar * +static const gchar * aac_codec_data_to_codec_id (const GstBuffer * buf) { - gchar *result; + const gchar *result; gint profile; /* default to MAIN */ @@ -1388,6 +1400,9 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) const gchar *mimetype; gint samplerate = 0, channels = 0; GstStructure *structure; + const GValue *codec_data = NULL; + const GstBuffer *buf = NULL; + const gchar *stream_format = NULL; mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad)); @@ -1411,6 +1426,10 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) audiocontext->bitdepth = 0; context->default_duration = 0; + codec_data = gst_structure_get_value (structure, "codec_data"); + if (codec_data) + buf = gst_value_get_buffer (codec_data); + /* TODO: - check if we handle all codecs by the spec, i.e. codec private * data and other settings * - add new formats @@ -1418,12 +1437,6 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) if (!strcmp (mimetype, "audio/mpeg")) { gint mpegversion = 0; - const GValue *codec_data; - const GstBuffer *buf = NULL; - - codec_data = gst_structure_get_value (structure, "codec_data"); - if (codec_data) - buf = gst_value_get_buffer (codec_data); gst_structure_get_int (structure, "mpegversion", &mpegversion); switch (mpegversion) { @@ -1463,35 +1476,43 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3); break; default: - return FALSE; + goto refuse_caps; } break; } case 2: - if (buf) { - context->codec_id = - g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG2 "%s", - aac_codec_data_to_codec_id (buf)); + case 4: + stream_format = gst_structure_get_string (structure, "stream-format"); + /* check this is raw aac */ + if (stream_format) { + if (strcmp (stream_format, "raw") != 0) { + GST_WARNING_OBJECT (mux, "AAC stream-format must be 'raw', not %s", + stream_format); + } } else { - GST_DEBUG_OBJECT (mux, "no AAC codec_data; not packetized"); - return FALSE; + GST_WARNING_OBJECT (mux, "AAC stream-format not specified, " + "assuming 'raw'"); } - break; - case 4: + if (buf) { - context->codec_id = - g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4 "%s", - aac_codec_data_to_codec_id (buf)); + if (mpegversion == 2) + context->codec_id = + g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG2 "%s", + aac_codec_data_to_codec_id (buf)); + else if (mpegversion == 4) + context->codec_id = + g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4 "%s", + aac_codec_data_to_codec_id (buf)); + else + g_assert_not_reached (); } else { GST_DEBUG_OBJECT (mux, "no AAC codec_data; not packetized"); - return FALSE; + goto refuse_caps; } break; default: - return FALSE; + goto refuse_caps; } - - return TRUE; } else if (!strcmp (mimetype, "audio/x-raw-int")) { gint width, depth; gint endianness = G_LITTLE_ENDIAN; @@ -1501,24 +1522,24 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) !gst_structure_get_int (structure, "depth", &depth) || !gst_structure_get_boolean (structure, "signed", &signedness)) { GST_DEBUG_OBJECT (mux, "broken caps, width/depth/signed field missing"); - return FALSE; + goto refuse_caps; } if (depth > 8 && !gst_structure_get_int (structure, "endianness", &endianness)) { GST_DEBUG_OBJECT (mux, "broken caps, no endianness specified"); - return FALSE; + goto refuse_caps; } if (width != depth) { GST_DEBUG_OBJECT (mux, "width must be same as depth!"); - return FALSE; + goto refuse_caps; } /* FIXME: where is this spec'ed out? (tpm) */ if ((width == 8 && signedness) || (width >= 16 && !signedness)) { GST_DEBUG_OBJECT (mux, "8-bit PCM must be unsigned, 16-bit PCM signed"); - return FALSE; + goto refuse_caps; } audiocontext->bitdepth = depth; @@ -1527,19 +1548,17 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) else context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE); - return TRUE; } else if (!strcmp (mimetype, "audio/x-raw-float")) { gint width; if (!gst_structure_get_int (structure, "width", &width)) { GST_DEBUG_OBJECT (mux, "broken caps, width field missing"); - return FALSE; + goto refuse_caps; } audiocontext->bitdepth = width; context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT); - return TRUE; } else if (!strcmp (mimetype, "audio/x-vorbis")) { const GValue *streamheader; @@ -1555,9 +1574,8 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) if (!vorbis_streamheader_to_codecdata (streamheader, context)) { GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL), ("vorbis stream headers missing or malformed")); - return FALSE; + goto refuse_caps; } - return TRUE; } else if (!strcmp (mimetype, "audio/x-flac")) { const GValue *streamheader; @@ -1572,9 +1590,8 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) if (!flac_streamheader_to_codecdata (streamheader, context)) { GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL), ("flac stream headers missing or malformed")); - return FALSE; + goto refuse_caps; } - return TRUE; } else if (!strcmp (mimetype, "audio/x-speex")) { const GValue *streamheader; @@ -1589,13 +1606,14 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) if (!speex_streamheader_to_codecdata (streamheader, context)) { GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL), ("speex stream headers missing or malformed")); - return FALSE; + goto refuse_caps; } - return TRUE; } else if (!strcmp (mimetype, "audio/x-ac3")) { context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3); - - return TRUE; + } else if (!strcmp (mimetype, "audio/x-eac3")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_EAC3); + } else if (!strcmp (mimetype, "audio/x-dts")) { + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_DTS); } else if (!strcmp (mimetype, "audio/x-tta")) { gint width; @@ -1606,7 +1624,6 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) audiocontext->bitdepth = width; context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_TTA); - return TRUE; } else if (!strcmp (mimetype, "audio/x-pn-realaudio")) { gint raversion; const GValue *mdpr_data; @@ -1623,7 +1640,7 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK); break; default: - return FALSE; + goto refuse_caps; } mdpr_data = gst_structure_get_value (structure, "mdpr_data"); @@ -1642,10 +1659,99 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) context->codec_priv_size = priv_data_size; } - return TRUE; + } else if (!strcmp (mimetype, "audio/x-wma") + || !strcmp (mimetype, "audio/x-alaw") + || !strcmp (mimetype, "audio/x-mulaw")) { + guint8 *codec_priv; + guint codec_priv_size; + guint16 format = 0; + gint block_align; + gint bitrate; + + if (samplerate == 0 || channels == 0) { + GST_WARNING_OBJECT (mux, "Missing channels/samplerate on caps"); + goto refuse_caps; + } + + if (!strcmp (mimetype, "audio/x-wma")) { + gint wmaversion; + gint depth; + + if (!gst_structure_get_int (structure, "wmaversion", &wmaversion) + || !gst_structure_get_int (structure, "block_align", &block_align) + || !gst_structure_get_int (structure, "bitrate", &bitrate)) { + GST_WARNING_OBJECT (mux, "Missing wmaversion/block_align/bitrate" + " on WMA caps"); + goto refuse_caps; + } + + switch (wmaversion) { + case 1: + format = GST_RIFF_WAVE_FORMAT_WMAV1; + break; + case 2: + format = GST_RIFF_WAVE_FORMAT_WMAV2; + break; + case 3: + format = GST_RIFF_WAVE_FORMAT_WMAV3; + break; + default: + GST_WARNING_OBJECT (mux, "Unexpected WMA version: %d", wmaversion); + goto refuse_caps; + } + + if (gst_structure_get_int (structure, "depth", &depth)) + audiocontext->bitdepth = depth; + } else if (!strcmp (mimetype, "audio/x-alaw") + || !strcmp (mimetype, "audio/x-mulaw")) { + audiocontext->bitdepth = 8; + if (!strcmp (mimetype, "audio/x-alaw")) + format = GST_RIFF_WAVE_FORMAT_ALAW; + else + format = GST_RIFF_WAVE_FORMAT_MULAW; + + block_align = channels; + bitrate = block_align * samplerate; + } + g_assert (format != 0); + + codec_priv_size = WAVEFORMATEX_SIZE; + if (buf) + codec_priv_size += GST_BUFFER_SIZE (buf); + + /* serialize waveformatex structure */ + codec_priv = g_malloc0 (codec_priv_size); + GST_WRITE_UINT16_LE (codec_priv, format); + GST_WRITE_UINT16_LE (codec_priv + 2, channels); + GST_WRITE_UINT32_LE (codec_priv + 4, samplerate); + GST_WRITE_UINT32_LE (codec_priv + 8, bitrate / 8); + GST_WRITE_UINT16_LE (codec_priv + 12, block_align); + GST_WRITE_UINT16_LE (codec_priv + 14, 0); + if (buf) + GST_WRITE_UINT16_LE (codec_priv + 16, GST_BUFFER_SIZE (buf)); + else + GST_WRITE_UINT16_LE (codec_priv + 16, 0); + + /* process codec private/initialization data, if any */ + if (buf) { + memcpy ((guint8 *) codec_priv + WAVEFORMATEX_SIZE, + GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); + } + + context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_ACM); + context->codec_priv = (gpointer) codec_priv; + context->codec_priv_size = codec_priv_size; } - return FALSE; + return TRUE; + + /* ERRORS */ +refuse_caps: + { + GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT, + GST_PAD_NAME (pad), caps); + return FALSE; + } } @@ -1733,32 +1839,55 @@ gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps) */ static GstPad * gst_matroska_mux_request_new_pad (GstElement * element, - GstPadTemplate * templ, const gchar * pad_name) + GstPadTemplate * templ, const gchar * req_name) { GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); GstMatroskaMux *mux = GST_MATROSKA_MUX (element); GstMatroskaPad *collect_pad; GstPad *newpad = NULL; gchar *name = NULL; + const gchar *pad_name = NULL; GstPadSetCapsFunction setcapsfunc = NULL; GstMatroskaTrackContext *context = NULL; + gint pad_id; - if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) { - name = g_strdup_printf ("audio_%d", mux->num_a_streams++); + 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 + * trying to add it */ + if (req_name != NULL && sscanf (req_name, "audio_%u", &pad_id) == 1) { + pad_name = req_name; + } else { + name = g_strdup_printf ("audio_%u", mux->num_a_streams++); + pad_name = name; + } setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_audio_pad_setcaps); context = (GstMatroskaTrackContext *) g_new0 (GstMatroskaTrackAudioContext, 1); context->type = GST_MATROSKA_TRACK_TYPE_AUDIO; context->name = g_strdup ("Audio"); - } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) { - name = g_strdup_printf ("video_%d", mux->num_v_streams++); + } else if (templ == gst_element_class_get_pad_template (klass, "video_%u")) { + /* don't mix named and unnamed pads, if the pad already exists we fail when + * trying to add it */ + if (req_name != NULL && sscanf (req_name, "video_%u", &pad_id) == 1) { + pad_name = req_name; + } else { + name = g_strdup_printf ("video_%u", mux->num_v_streams++); + pad_name = name; + } setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_video_pad_setcaps); context = (GstMatroskaTrackContext *) g_new0 (GstMatroskaTrackVideoContext, 1); context->type = GST_MATROSKA_TRACK_TYPE_VIDEO; context->name = g_strdup ("Video"); - } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%d")) { - name = g_strdup_printf ("subtitle_%d", mux->num_t_streams++); + } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%u")) { + /* don't mix named and unnamed pads, if the pad already exists we fail when + * trying to add it */ + if (req_name != NULL && sscanf (req_name, "subtitle_%u", &pad_id) == 1) { + pad_name = req_name; + } else { + name = g_strdup_printf ("subtitle_%u", mux->num_t_streams++); + pad_name = name; + } setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_subtitle_pad_setcaps); context = (GstMatroskaTrackContext *) g_new0 (GstMatroskaTrackSubtitleContext, 1); @@ -1769,7 +1898,7 @@ gst_matroska_mux_request_new_pad (GstElement * element, return NULL; } - newpad = gst_pad_new_from_template (templ, name); + newpad = gst_pad_new_from_template (templ, pad_name); g_free (name); collect_pad = (GstMatroskaPad *) gst_collect_pads_add_pad_full (mux->collect, newpad, @@ -1793,10 +1922,22 @@ gst_matroska_mux_request_new_pad (GstElement * element, gst_pad_set_setcaps_function (newpad, setcapsfunc); gst_pad_set_active (newpad, TRUE); - gst_element_add_pad (element, newpad); + if (!gst_element_add_pad (element, newpad)) + goto pad_add_failed; + mux->num_streams++; + GST_DEBUG_OBJECT (newpad, "Added new request pad"); + return newpad; + + /* ERROR cases */ +pad_add_failed: + { + GST_WARNING_OBJECT (mux, "Adding the new pad '%s' failed", pad_name); + gst_object_unref (newpad); + return NULL; + } } /** @@ -1952,6 +2093,7 @@ static void gst_matroska_mux_start (GstMatroskaMux * mux) { GstEbmlWrite *ebml = mux->ebml_write; + const gchar *doctype; guint32 seekhead_id[] = { GST_MATROSKA_ID_SEGMENTINFO, GST_MATROSKA_ID_TRACKS, GST_MATROSKA_ID_CUES, @@ -1966,27 +2108,58 @@ gst_matroska_mux_start (GstMatroskaMux * mux) guint32 segment_uid[4]; GTimeVal time = { 0, 0 }; + if (!strcmp (mux->doctype, GST_MATROSKA_DOCTYPE_WEBM)) { + ebml->caps = gst_caps_new_simple ("video/webm", NULL); + } else { + ebml->caps = gst_caps_new_simple ("video/x-matroska", NULL); + } /* we start with a EBML header */ - gst_ebml_write_header (ebml, "matroska", mux->matroska_version); + doctype = mux->doctype; + GST_INFO_OBJECT (ebml, "DocType: %s, Version: %d", + doctype, mux->doctype_version); + gst_ebml_write_header (ebml, doctype, mux->doctype_version); + + /* the rest of the header is cached */ + gst_ebml_write_set_cache (ebml, 0x1000); /* start a segment */ mux->segment_pos = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENT); mux->segment_master = ebml->pos; - /* the rest of the header is cached */ - gst_ebml_write_set_cache (ebml, 0x1000); + if (!mux->streamable) { + /* seekhead (table of contents) - we set the positions later */ + mux->seekhead_pos = ebml->pos; + master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD); + for (i = 0; seekhead_id[i] != 0; i++) { + child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKENTRY); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, seekhead_id[i]); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION, -1); + gst_ebml_write_master_finish (ebml, child); + } + gst_ebml_write_master_finish (ebml, master); + } + + if (mux->streamable) { + const GstTagList *tags; + + /* 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; + + GST_DEBUG ("Writing tags"); - /* seekhead (table of contents) - we set the positions later */ - mux->seekhead_pos = ebml->pos; - master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD); - for (i = 0; seekhead_id[i] != 0; i++) { - child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKENTRY); - gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, seekhead_id[i]); - gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION, -1); - gst_ebml_write_master_finish (ebml, child); + /* 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); + } } - gst_ebml_write_master_finish (ebml, master); /* segment info */ mux->info_pos = ebml->pos; @@ -1999,30 +2172,31 @@ gst_matroska_mux_start (GstMatroskaMux * mux) gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TIMECODESCALE, mux->time_scale); mux->duration_pos = ebml->pos; /* get duration */ - for (collected = mux->collect->data; collected; - collected = g_slist_next (collected)) { - GstMatroskaPad *collect_pad; - GstFormat format = GST_FORMAT_TIME; - GstPad *thepad; - gint64 trackduration; - - collect_pad = (GstMatroskaPad *) collected->data; - thepad = collect_pad->collect.pad; - - /* Query the total length of the track. */ - GST_DEBUG_OBJECT (thepad, "querying peer duration"); - if (gst_pad_query_peer_duration (thepad, &format, &trackduration)) { - GST_DEBUG_OBJECT (thepad, "duration: %" GST_TIME_FORMAT, - GST_TIME_ARGS (trackduration)); - if (trackduration != GST_CLOCK_TIME_NONE && trackduration > duration) { - duration = (GstClockTime) trackduration; + if (!mux->streamable) { + for (collected = mux->collect->data; collected; + collected = g_slist_next (collected)) { + GstMatroskaPad *collect_pad; + GstFormat format = GST_FORMAT_TIME; + GstPad *thepad; + gint64 trackduration; + + collect_pad = (GstMatroskaPad *) collected->data; + thepad = collect_pad->collect.pad; + + /* Query the total length of the track. */ + GST_DEBUG_OBJECT (thepad, "querying peer duration"); + if (gst_pad_query_peer_duration (thepad, &format, &trackduration)) { + GST_DEBUG_OBJECT (thepad, "duration: %" GST_TIME_FORMAT, + GST_TIME_ARGS (trackduration)); + if (trackduration != GST_CLOCK_TIME_NONE && trackduration > duration) { + duration = (GstClockTime) trackduration; + } } } + gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION, + gst_guint64_to_gdouble (duration) / + gst_guint64_to_gdouble (mux->time_scale)); } - gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION, - gst_guint64_to_gdouble (duration) / - gst_guint64_to_gdouble (mux->time_scale)); - gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_MUXINGAPP, "GStreamer plugin version " PACKAGE_VERSION); if (mux->writing_app && mux->writing_app[0]) { @@ -2050,12 +2224,16 @@ 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 */ + 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); /* lastly, flush the cache */ - gst_ebml_write_flush_cache (ebml); + gst_ebml_write_flush_cache (ebml, FALSE, 0); } static void @@ -2063,15 +2241,15 @@ gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag, gpointer data) { /* TODO: more sensible tag mappings */ - struct + static const struct { - gchar *matroska_tagname; - gchar *gstreamer_tagname; + const gchar *matroska_tagname; + const gchar *gstreamer_tagname; } tag_conv[] = { { GST_MATROSKA_TAG_ID_TITLE, GST_TAG_TITLE}, { - GST_MATROSKA_TAG_ID_AUTHOR, GST_TAG_ARTIST}, { + GST_MATROSKA_TAG_ID_ARTIST, GST_TAG_ARTIST}, { GST_MATROSKA_TAG_ID_ALBUM, GST_TAG_ALBUM}, { GST_MATROSKA_TAG_ID_COMMENTS, GST_TAG_COMMENT}, { GST_MATROSKA_TAG_ID_BITSPS, GST_TAG_BITRATE}, { @@ -2164,7 +2342,7 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) } gst_ebml_write_master_finish (ebml, master); - gst_ebml_write_flush_cache (ebml); + gst_ebml_write_flush_cache (ebml, FALSE, GST_CLOCK_TIME_NONE); } /* tags */ @@ -2232,7 +2410,8 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) collect_pad = (GstMatroskaPad *) collected->data; - GST_DEBUG_OBJECT (mux, "Pad %" GST_PTR_FORMAT " start ts %" GST_TIME_FORMAT + GST_DEBUG_OBJECT (mux, + "Pad %" GST_PTR_FORMAT " start ts %" GST_TIME_FORMAT " end ts %" GST_TIME_FORMAT, collect_pad, GST_TIME_ARGS (collect_pad->start_ts), GST_TIME_ARGS (collect_pad->end_ts)); @@ -2243,7 +2422,8 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts); if (collect_pad->duration < min_duration) collect_pad->duration = min_duration; - GST_DEBUG_OBJECT (collect_pad, "final track duration: %" GST_TIME_FORMAT, + GST_DEBUG_OBJECT (collect_pad, + "final track duration: %" GST_TIME_FORMAT, GST_TIME_ARGS (collect_pad->duration)); } @@ -2268,7 +2448,7 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 8); gst_ebml_write_seek (ebml, my_pos); } - + GST_DEBUG_OBJECT (mux, "finishing segment"); /* finish segment - this also writes element length */ gst_ebml_write_master_finish (ebml, mux->segment_pos); } @@ -2279,7 +2459,7 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) * @mux: #GstMatroskaMux * @popped: True if at least one buffer was popped from #GstCollectPads * - * Find a pad with the oldest data + * Find a pad with the oldest data * (data from this pad should be written first). * * Returns: Selected pad. @@ -2301,8 +2481,33 @@ gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped) collect_pad->buffer = gst_collect_pads_pop (mux->collect, (GstCollectData *) collect_pad); - if (collect_pad->buffer != NULL) + 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_metadata_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 */ @@ -2326,10 +2531,10 @@ gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped) * @flags: Buffer flags. * * Create a buffer containing buffer header. - * + * * Returns: New buffer. */ -GstBuffer * +static GstBuffer * gst_matroska_mux_create_buffer_header (GstMatroskaTrackContext * track, gint16 relative_timestamp, int flags) { @@ -2347,6 +2552,10 @@ gst_matroska_mux_create_buffer_header (GstMatroskaTrackContext * track, return hdr; } +#define DIRAC_PARSE_CODE_SEQUENCE_HEADER 0x00 +#define DIRAC_PARSE_CODE_END_OF_SEQUENCE 0x10 +#define DIRAC_PARSE_CODE_IS_PICTURE(x) ((x & 0x08) != 0) + static GstBuffer * gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux, GstMatroskaPad * collect_pad, GstBuffer * buf) @@ -2358,33 +2567,37 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux, guint8 parse_code; guint32 next_parse_offset; GstBuffer *ret = NULL; - gboolean is_picture = FALSE; + gboolean is_muxing_unit = FALSE; if (GST_BUFFER_SIZE (buf) < 13) { gst_buffer_unref (buf); return ret; } - /* Check if this buffer contains a picture packet */ + /* Check if this buffer contains a picture or end-of-sequence packet */ while (size >= 13) { - if (GST_READ_UINT32_BE (data) != 0x42424344) { + if (GST_READ_UINT32_BE (data) != 0x42424344 /* 'BBCD' */ ) { gst_buffer_unref (buf); return ret; } parse_code = GST_READ_UINT8 (data + 4); - if (parse_code == 0x00) { + if (parse_code == DIRAC_PARSE_CODE_SEQUENCE_HEADER) { if (ctx->dirac_unit) { gst_buffer_unref (ctx->dirac_unit); ctx->dirac_unit = NULL; } - } else if (parse_code & 0x08) { - is_picture = TRUE; + } else if (DIRAC_PARSE_CODE_IS_PICTURE (parse_code) || + parse_code == DIRAC_PARSE_CODE_END_OF_SEQUENCE) { + is_muxing_unit = TRUE; break; } next_parse_offset = GST_READ_UINT32_BE (data + 5); + if (G_UNLIKELY (next_parse_offset == 0 || next_parse_offset > size)) + break; + data += next_parse_offset; size -= next_parse_offset; } @@ -2394,7 +2607,7 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux, else ctx->dirac_unit = gst_buffer_ref (buf); - if (is_picture) { + if (is_muxing_unit) { ret = gst_buffer_make_metadata_writable (ctx->dirac_unit); ctx->dirac_unit = NULL; gst_buffer_copy_metadata (ret, buf, @@ -2409,6 +2622,36 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux, return ret; } +static void +gst_matroska_mux_stop_streamheader (GstMatroskaMux * mux) +{ + GstCaps *caps; + GstStructure *s; + GValue streamheader = { 0 }; + GValue bufval = { 0 }; + GstBuffer *streamheader_buffer; + GstEbmlWrite *ebml = mux->ebml_write; + + streamheader_buffer = gst_ebml_stop_streamheader (ebml); + if (!strcmp (mux->doctype, GST_MATROSKA_DOCTYPE_WEBM)) { + caps = gst_caps_new_simple ("video/webm", NULL); + } else { + caps = gst_caps_new_simple ("video/x-matroska", NULL); + } + 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_value_set_buffer (&bufval, streamheader_buffer); + gst_value_array_append_value (&streamheader, &bufval); + g_value_unset (&bufval); + gst_structure_set_value (s, "streamheader", &streamheader); + g_value_unset (&streamheader); + gst_caps_replace (&ebml->caps, caps); + gst_buffer_unref (streamheader_buffer); + gst_caps_unref (caps); +} + /** * gst_matroska_mux_write_data: * @mux: #GstMatroskaMux @@ -2473,25 +2716,45 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) } if (mux->cluster) { - /* start a new cluster every two seconds or at keyframe */ - if (mux->cluster_time + GST_SECOND * 2 < GST_BUFFER_TIMESTAMP (buf) - || is_video_keyframe) { + /* start a new cluster at every keyframe, at every GstForceKeyUnit event, + * or when we may be reaching the limit of the relative timestamp */ + if (mux->cluster_time + + mux->max_cluster_duration < GST_BUFFER_TIMESTAMP (buf) + || is_video_keyframe || mux->force_key_unit_event) { + if (!mux->streamable) + gst_ebml_write_master_finish (ebml, mux->cluster); + + /* Forward the GstForceKeyUnit event after finishing the cluster */ + if (mux->force_key_unit_event) { + gst_pad_push_event (mux->srcpad, mux->force_key_unit_event); + mux->force_key_unit_event = NULL; + } - gst_ebml_write_master_finish (ebml, mux->cluster); + mux->prev_cluster_size = ebml->pos - mux->cluster_pos; mux->cluster_pos = ebml->pos; + gst_ebml_write_set_cache (ebml, 0x20); mux->cluster = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER); gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE, - GST_BUFFER_TIMESTAMP (buf) / mux->time_scale); + gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (buf), 1, + mux->time_scale)); + GST_LOG_OBJECT (mux, "cluster timestamp %" G_GUINT64_FORMAT, + gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (buf), 1, + mux->time_scale)); + gst_ebml_write_flush_cache (ebml, TRUE, GST_BUFFER_TIMESTAMP (buf)); mux->cluster_time = GST_BUFFER_TIMESTAMP (buf); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_PREVSIZE, + mux->prev_cluster_size); } } else { /* first cluster */ mux->cluster_pos = ebml->pos; + gst_ebml_write_set_cache (ebml, 0x20); mux->cluster = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER); gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE, - GST_BUFFER_TIMESTAMP (buf) / mux->time_scale); + gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (buf), 1, mux->time_scale)); + gst_ebml_write_flush_cache (ebml, TRUE, GST_BUFFER_TIMESTAMP (buf)); mux->cluster_time = GST_BUFFER_TIMESTAMP (buf); } @@ -2499,90 +2762,103 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) if (GST_BUFFER_DURATION_IS_VALID (buf)) collect_pad->duration += GST_BUFFER_DURATION (buf); - /* We currently write an index entry for each keyframe in a - * video track or one entry for each cluster in an audio track - * for audio only files. This can be largely improved, such as doing - * one for each keyframe or each second (for all-keyframe - * streams), only the *first* video track. But that'll come later... */ + /* We currently write index entries for all video tracks or for the audio + * track in a single-track audio file. This could be improved by keeping the + * index only for the *first* video track. */ /* TODO: index is useful for every track, should contain the number of - * the block in the cluster which contains the timestamp + * the block in the cluster which contains the timestamp, should also work + * for files with multiple audio tracks. */ - if (is_video_keyframe) { - GstMatroskaIndex *idx; - - if (mux->num_indexes % 32 == 0) { - mux->index = g_renew (GstMatroskaIndex, mux->index, - mux->num_indexes + 32); + if (!mux->streamable && + (is_video_keyframe || + ((collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_AUDIO) && + (mux->num_streams == 1)))) { + gint last_idx = -1; + + if (mux->min_index_interval != 0) { + for (last_idx = mux->num_indexes - 1; last_idx >= 0; last_idx--) { + if (mux->index[last_idx].track == collect_pad->track->num) + break; + } } - idx = &mux->index[mux->num_indexes++]; - idx->pos = mux->cluster_pos; - idx->time = GST_BUFFER_TIMESTAMP (buf); - idx->track = collect_pad->track->num; - } else if ((collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_AUDIO) && - (mux->num_streams == 1)) { - GstMatroskaIndex *idx; + if (last_idx < 0 || mux->min_index_interval == 0 || + (GST_CLOCK_DIFF (mux->index[last_idx].time, GST_BUFFER_TIMESTAMP (buf)) + >= mux->min_index_interval)) { + GstMatroskaIndex *idx; - if (mux->num_indexes % 32 == 0) { - mux->index = g_renew (GstMatroskaIndex, mux->index, - mux->num_indexes + 32); - } - idx = &mux->index[mux->num_indexes++]; + if (mux->num_indexes % 32 == 0) { + mux->index = g_renew (GstMatroskaIndex, mux->index, + mux->num_indexes + 32); + } + idx = &mux->index[mux->num_indexes++]; - idx->pos = mux->cluster_pos; - idx->time = GST_BUFFER_TIMESTAMP (buf); - idx->track = collect_pad->track->num; + idx->pos = mux->cluster_pos; + idx->time = GST_BUFFER_TIMESTAMP (buf); + idx->track = collect_pad->track->num; + } } /* Check if the duration differs from the default duration. */ write_duration = FALSE; - block_duration = GST_BUFFER_DURATION (buf); + block_duration = 0; if (GST_BUFFER_DURATION_IS_VALID (buf)) { - if (block_duration != collect_pad->track->default_duration) { + block_duration = gst_util_uint64_scale (GST_BUFFER_DURATION (buf), + 1, mux->time_scale); + + /* small difference should be ok. */ + if (block_duration > collect_pad->default_duration_scaled + 1 || + block_duration < collect_pad->default_duration_scaled - 1) { write_duration = TRUE; } } - /* write the block, for matroska v2 use SimpleBlock if possible + /* write the block, for doctype v2 use SimpleBlock if possible * one slice (*breath*). * FIXME: Need to do correct lacing! */ relative_timestamp64 = GST_BUFFER_TIMESTAMP (buf) - mux->cluster_time; if (relative_timestamp64 >= 0) { /* round the timestamp */ - relative_timestamp64 += mux->time_scale / 2; + relative_timestamp64 += gst_util_uint64_scale (mux->time_scale, 1, 2); } else { /* round the timestamp */ - relative_timestamp64 -= mux->time_scale / 2; + relative_timestamp64 -= gst_util_uint64_scale (mux->time_scale, 1, 2); } - relative_timestamp = relative_timestamp64 / (gint64) mux->time_scale; - if (mux->matroska_version > 1 && !write_duration) { + relative_timestamp = gst_util_uint64_scale (relative_timestamp64, 1, + mux->time_scale); + if (mux->doctype_version > 1 && !write_duration) { int flags = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) ? 0 : 0x80; hdr = gst_matroska_mux_create_buffer_header (collect_pad->track, relative_timestamp, flags); + gst_ebml_write_set_cache (ebml, 0x40); gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_SIMPLEBLOCK, GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr)); gst_ebml_write_buffer (ebml, hdr); + gst_ebml_write_flush_cache (ebml, FALSE, GST_BUFFER_TIMESTAMP (buf)); gst_ebml_write_buffer (ebml, buf); return gst_ebml_last_write_result (ebml); } else { + gst_ebml_write_set_cache (ebml, GST_BUFFER_SIZE (buf) * 2); + /* write and call order slightly unnatural, + * but avoids seek and minizes pushing */ blockgroup = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_BLOCKGROUP); hdr = gst_matroska_mux_create_buffer_header (collect_pad->track, relative_timestamp, 0); + if (write_duration) + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_BLOCKDURATION, block_duration); gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_BLOCK, GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr)); gst_ebml_write_buffer (ebml, hdr); + gst_ebml_write_master_finish_full (ebml, blockgroup, GST_BUFFER_SIZE (buf)); + gst_ebml_write_flush_cache (ebml, FALSE, GST_BUFFER_TIMESTAMP (buf)); gst_ebml_write_buffer (ebml, buf); - if (write_duration) { - gst_ebml_write_uint (ebml, GST_MATROSKA_ID_BLOCKDURATION, - block_duration / mux->time_scale); - } - gst_ebml_write_master_finish (ebml, blockgroup); + return gst_ebml_last_write_result (ebml); } } @@ -2601,9 +2877,10 @@ static GstFlowReturn gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data) { GstMatroskaMux *mux = GST_MATROSKA_MUX (user_data); + GstEbmlWrite *ebml = mux->ebml_write; GstMatroskaPad *best; gboolean popped; - GstFlowReturn ret; + GstFlowReturn ret = GST_FLOW_OK; GST_DEBUG_OBJECT (mux, "Collected pads"); @@ -2615,7 +2892,9 @@ gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data) return GST_FLOW_ERROR; } mux->state = GST_MATROSKA_MUX_STATE_HEADER; + gst_ebml_start_streamheader (ebml); gst_matroska_mux_start (mux); + gst_matroska_mux_stop_streamheader (mux); mux->state = GST_MATROSKA_MUX_STATE_DATA; } @@ -2625,8 +2904,15 @@ gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_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..."); - gst_matroska_mux_finish (mux); + 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; @@ -2728,8 +3014,14 @@ gst_matroska_mux_set_property (GObject * object, g_free (mux->writing_app); mux->writing_app = g_value_dup_string (value); break; - case ARG_MATROSKA_VERSION: - mux->matroska_version = g_value_get_int (value); + case ARG_DOCTYPE_VERSION: + mux->doctype_version = g_value_get_int (value); + break; + case ARG_MIN_INDEX_INTERVAL: + mux->min_index_interval = g_value_get_int64 (value); + break; + case ARG_STREAMABLE: + mux->streamable = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -2750,18 +3042,17 @@ gst_matroska_mux_get_property (GObject * object, case ARG_WRITING_APP: g_value_set_string (value, mux->writing_app); break; - case ARG_MATROSKA_VERSION: - g_value_set_int (value, mux->matroska_version); + case ARG_DOCTYPE_VERSION: + g_value_set_int (value, mux->doctype_version); + break; + case ARG_MIN_INDEX_INTERVAL: + g_value_set_int64 (value, mux->min_index_interval); + break; + case ARG_STREAMABLE: + g_value_set_boolean (value, mux->streamable); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } - -gboolean -gst_matroska_mux_plugin_init (GstPlugin * plugin) -{ - return gst_element_register (plugin, "matroskamux", - GST_RANK_PRIMARY, GST_TYPE_MATROSKA_MUX); -}