X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gst%2Fmatroska%2Fmatroska-mux.c;h=4cb87da7f029e85b6cc2248e8e9a0c279b4f1403;hb=04b4d30f2cf7f8196703e14647cbb88b7ec1ad37;hp=31fe40bfc3b5658b3c3aecdc1ffaecf5fcc310ff;hpb=759a3507d7d2a77cec6111dd34d6a67f2199dd59;p=platform%2Fupstream%2Fgst-plugins-good.git diff --git a/gst/matroska/matroska-mux.c b/gst/matroska/matroska-mux.c index 31fe40b..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 * @@ -49,12 +50,15 @@ #include #include +#include #include #include #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 @@ -95,14 +99,14 @@ static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src", */ 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 "; " @@ -126,8 +130,8 @@ static GstStaticPadTemplate videosink_templ = COMMON_VIDEO_CAPS "; " "video/x-vp8, " COMMON_VIDEO_CAPS "; " - "video/x-raw-yuv, " - "format = (fourcc) { YUY2, I420, YV12, UYVY, AYUV }, " + "video/x-raw, " + "format = (string) { YUY2, I420, YV12, UYVY, AYUV }, " COMMON_VIDEO_CAPS "; " "video/x-wmv, " "wmvversion = (int) [ 1, 3 ], " COMMON_VIDEO_CAPS) ); @@ -140,16 +144,16 @@ 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 }, " + "stream-format = (string) raw, " COMMON_AUDIO_CAPS "; " "audio/x-ac3, " COMMON_AUDIO_CAPS "; " @@ -163,32 +167,9 @@ static GstStaticPadTemplate audiosink_templ = COMMON_AUDIO_CAPS "; " "audio/x-speex, " COMMON_AUDIO_CAPS "; " - "audio/x-raw-int, " - "width = (int) 8, " - "depth = (int) 8, " - "signed = (boolean) false, " - COMMON_AUDIO_CAPS ";" - "audio/x-raw-int, " - "width = (int) 16, " - "depth = (int) 16, " - "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, " - "signed = (boolean) true, " - COMMON_AUDIO_CAPS ";" - "audio/x-raw-int, " - "width = (int) 24, " - "depth = (int) 24, " - "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, " - "signed = (boolean) true, " - COMMON_AUDIO_CAPS ";" - "audio/x-raw-int, " - "width = (int) 32, " - "depth = (int) 32, " - "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, " - "signed = (boolean) true, " - COMMON_AUDIO_CAPS ";" - "audio/x-raw-float, " - "width = (int) [ 32, 64 ], " - "endianness = (int) LITTLE_ENDIAN, " + "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 }, " @@ -197,35 +178,46 @@ static GstStaticPadTemplate audiosink_templ = "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) + 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_%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); -static void gst_matroska_mux_add_interfaces (GType type); - -GST_BOILERPLATE_FULL (GstMatroskaMux, gst_matroska_mux, GstElement, - GST_TYPE_ELEMENT, gst_matroska_mux_add_interfaces); +#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_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, - GstEvent * event); + GstObject * parent, GstEvent * event); static GstPad *gst_matroska_mux_request_new_pad (GstElement * element, - GstPadTemplate * templ, const gchar * name); + GstPadTemplate * templ, const gchar * name, const GstCaps * caps); static void gst_matroska_mux_release_pad (GstElement * element, GstPad * pad); /* gst internal change state handler */ @@ -254,19 +246,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_add_interfaces (GType type) -{ - static const GInterfaceInfo tag_setter_info = { NULL, NULL, NULL }; - - g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info); -} - static void -gst_matroska_mux_base_init (gpointer g_class) -{ -} +gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag, + gpointer data); static void gst_matroska_mux_class_init (GstMatroskaMuxClass * klass) @@ -285,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 "); @@ -327,6 +309,103 @@ gst_matroska_mux_class_init (GstMatroskaMuxClass * klass) GST_DEBUG_FUNCPTR (gst_matroska_mux_release_pad); } +/** + * Start of pad option handler code + */ +#define DEFAULT_PAD_FRAME_DURATION TRUE +#define DEFAULT_PAD_FRAME_DURATION_VP8 FALSE + +enum +{ + PROP_PAD_0, + PROP_PAD_FRAME_DURATION +}; + +typedef struct +{ + GstPad parent; + gboolean frame_duration; + gboolean frame_duration_user; +} GstMatroskamuxPad; + +static void gst_matroskamux_pad_class_init (GstPadClass * klass); + +static GType +gst_matroskamux_pad_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) { + type = g_type_register_static_simple (GST_TYPE_PAD, + g_intern_static_string ("GstMatroskamuxPad"), sizeof (GstPadClass), + (GClassInitFunc) gst_matroskamux_pad_class_init, + sizeof (GstMatroskamuxPad), NULL, 0); + } + return type; +} + +#define GST_TYPE_MATROSKAMUX_PAD (gst_matroskamux_pad_get_type()) +#define GST_MATROSKAMUX_PAD(pad) (G_TYPE_CHECK_INSTANCE_CAST((pad),GST_TYPE_MATROSKAMUX_PAD,GstMatroskamuxPad)) +#define GST_MATROSKAMUX_PAD_CAST(pad) ((GstMatroskamuxPad *) pad) +#define GST_IS_MATROSKAMUX_PAD(pad) (G_TYPE_CHECK_INSTANCE_TYPE((pad),GST_TYPE_MATROSKAMUX_PAD)) + +static void +gst_matroskamux_pad_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstMatroskamuxPad *pad = GST_MATROSKAMUX_PAD (object); + + switch (prop_id) { + case PROP_PAD_FRAME_DURATION: + g_value_set_boolean (value, pad->frame_duration); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_matroskamux_pad_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstMatroskamuxPad *pad = GST_MATROSKAMUX_PAD (object); + + switch (prop_id) { + case PROP_PAD_FRAME_DURATION: + pad->frame_duration = g_value_get_boolean (value); + pad->frame_duration_user = TRUE; + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_matroskamux_pad_class_init (GstPadClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->set_property = gst_matroskamux_pad_set_property; + gobject_class->get_property = gst_matroskamux_pad_get_property; + + g_object_class_install_property (gobject_class, PROP_PAD_FRAME_DURATION, + g_param_spec_boolean ("frame-duration", "Frame duration", + "Default frame duration", DEFAULT_PAD_FRAME_DURATION, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_matroskamux_pad_init (GstMatroskamuxPad * pad) +{ + pad->frame_duration = DEFAULT_PAD_FRAME_DURATION; + pad->frame_duration_user = FALSE; +} + +/* + * End of pad option handler code + **/ /** * gst_matroska_mux_init: @@ -336,21 +415,24 @@ gst_matroska_mux_class_init (GstMatroskaMuxClass * klass) * Matroska muxer constructor. */ static void -gst_matroska_mux_init (GstMatroskaMux * mux, GstMatroskaMuxClass * g_class) +gst_matroska_mux_init (GstMatroskaMux * mux) { GstPadTemplate *templ; templ = - gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src"); + gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (mux), "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); - 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; @@ -384,6 +466,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) @@ -465,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; @@ -490,7 +568,7 @@ gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full) break; default: g_assert_not_reached (); - break; + return; } context->type = type; @@ -498,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; @@ -512,9 +589,9 @@ gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full) * Release resources of a matroska collect pad. */ static void -gst_matroska_pad_free (GstMatroskaPad * collect_pad) +gst_matroska_pad_free (GstPad * collect_pad) { - gst_matroska_pad_reset (collect_pad, TRUE); + gst_matroska_pad_reset ((GstMatroskaPad *) collect_pad, TRUE); } @@ -565,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; } /** @@ -577,7 +661,8 @@ gst_matroska_mux_reset (GstElement * element) * Returns: #TRUE on success. */ static gboolean -gst_matroska_mux_handle_src_event (GstPad * pad, GstEvent * event) +gst_matroska_mux_handle_src_event (GstPad * pad, GstObject * parent, + GstEvent * event) { GstEventType type; @@ -591,9 +676,58 @@ gst_matroska_mux_handle_src_event (GstPad * pad, GstEvent * event) break; } - return gst_pad_event_default (pad, event); + 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. @@ -604,28 +738,40 @@ gst_matroska_mux_handle_src_event (GstPad * pad, GstEvent * event) * Returns: #TRUE on success. */ static gboolean -gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event) +gst_matroska_mux_handle_sink_event (GstCollectPads2 * pads, + GstCollectData2 * data, GstEvent * event, gpointer user_data) { - GstMatroskaTrackContext *context; GstMatroskaPad *collect_pad; + GstMatroskaTrackContext *context; GstMatroskaMux *mux; + GstPad *pad; GstTagList *list; gboolean ret = TRUE; - mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad)); + 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; + + collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad); + gst_event_parse_caps (event, &caps); + + ret = collect_pad->capsfunc (pad, caps); + gst_event_unref (event); + event = NULL; + break; + } case GST_EVENT_TAG:{ gchar *lang = NULL; 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; @@ -644,29 +790,91 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event) gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), list, gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (mux))); - /* handled this, don't want collectpads to forward it downstream */ - ret = FALSE; gst_event_unref (event); + /* handled this, don't want collectpads to forward it downstream */ + event = NULL; + ret = TRUE; break; } - case GST_EVENT_NEWSEGMENT: - /* We don't support NEWSEGMENT events */ - ret = FALSE; + case GST_EVENT_TOC:{ + GstToc *toc; + + 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:{ + 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; + } 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); + } + } + /* fall through */ default: break; } - /* now GstCollectPads can take care of the rest, e.g. EOS */ - if (ret) - ret = mux->collect_event (pad, event); - - gst_object_unref (mux); + 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: @@ -687,7 +895,7 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps) GstStructure *structure; const gchar *mimetype; const GValue *value = NULL; - const GstBuffer *codec_buf = NULL; + GstBuffer *codec_buf = NULL; gint width, height, pixel_width, pixel_height; gint fps_d, fps_n; gboolean interlaced = FALSE; @@ -724,7 +932,15 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps) videocontext->pixel_width = width; videocontext->pixel_height = height; - if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d) + + /* set vp8 defaults or let user override it */ + if (GST_MATROSKAMUX_PAD_CAST (pad)->frame_duration_user == FALSE + && (!strcmp (mimetype, "video/x-vp8"))) + GST_MATROSKAMUX_PAD_CAST (pad)->frame_duration = + DEFAULT_PAD_FRAME_DURATION_VP8; + + if (GST_MATROSKAMUX_PAD_CAST (pad)->frame_duration + && gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d) && fps_n > 0) { context->default_duration = gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n); @@ -763,21 +979,26 @@ skip_details: /* extract codec_data, may turn out needed */ value = gst_structure_get_value (structure, "codec_data"); if (value) - codec_buf = gst_value_get_buffer (value); + codec_buf = (GstBuffer *) gst_value_get_buffer (value); /* find type */ - 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); + if (!strcmp (mimetype, "video/x-raw")) { + const gchar *fstr; + 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") || !strcmp (mimetype, "video/x-dv") || !strcmp (mimetype, "video/x-h263") || !strcmp (mimetype, "video/x-msmpeg") - || !strcmp (mimetype, "video/x-wmv")) { + || !strcmp (mimetype, "video/x-wmv") + || !strcmp (mimetype, "image/jpeg")) { gst_riff_strf_vids *bih; gint size = sizeof (gst_riff_strf_vids); guint32 fourcc = 0; @@ -822,9 +1043,11 @@ skip_details: } } else if (!strcmp (mimetype, "video/x-wmv")) { gint wmvversion; - guint32 format; - if (gst_structure_get_fourcc (structure, "format", &format)) { - fourcc = format; + const gchar *fstr; + + fstr = gst_structure_get_string (structure, "format"); + if (fstr && strlen (fstr) == 4) { + fourcc = GST_STR_FOURCC (fstr); } else if (gst_structure_get_int (structure, "wmvversion", &wmvversion)) { if (wmvversion == 2) { fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '2'); @@ -834,6 +1057,8 @@ skip_details: fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '3'); } } + } else if (!strcmp (mimetype, "image/jpeg")) { + fourcc = GST_MAKE_FOURCC ('M', 'J', 'P', 'G'); } if (!fourcc) @@ -851,42 +1076,34 @@ skip_details: /* process codec private/initialization data, if any */ if (codec_buf) { - size += GST_BUFFER_SIZE (codec_buf); + size += gst_buffer_get_size (codec_buf); bih = g_realloc (bih, size); 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)); + gst_buffer_extract (codec_buf, 0, + (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_SIZE (codec_buf); + context->codec_priv_size = gst_buffer_get_size (codec_buf); context->codec_priv = g_malloc0 (context->codec_priv_size); - memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf), - context->codec_priv_size); + gst_buffer_extract (codec_buf, 0, context->codec_priv, -1); } } 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)) { @@ -895,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; @@ -918,10 +1138,10 @@ skip_details: /* global headers may be in codec data */ if (codec_buf != NULL) { - context->codec_priv_size = GST_BUFFER_SIZE (codec_buf); + 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); - memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf), - context->codec_priv_size); + gst_buffer_extract (codec_buf, 0, context->codec_priv, -1); } } else if (!strcmp (mimetype, "video/x-msmpeg")) { msmpeg43: @@ -934,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; @@ -956,11 +1180,12 @@ skip_details: GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data); - priv_data_size = GST_BUFFER_SIZE (codec_data_buf); + priv_data_size = gst_buffer_get_size (codec_data_buf); priv_data = g_malloc0 (priv_data_size); - memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size); + 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; } @@ -1017,12 +1242,12 @@ xiphN_streamheader_to_codecdata (const GValue * streamheader, priv_data_size = 1; if (bufarr->len > 0) { for (i = 0; i < bufarr->len - 1; i++) { - priv_data_size += GST_BUFFER_SIZE (buf[i]) / 0xff + 1; + priv_data_size += gst_buffer_get_size (buf[i]) / 0xff + 1; } } for (i = 0; i < bufarr->len; ++i) { - priv_data_size += GST_BUFFER_SIZE (buf[i]); + priv_data_size += gst_buffer_get_size (buf[i]); } priv_data = g_malloc0 (priv_data_size); @@ -1032,19 +1257,19 @@ xiphN_streamheader_to_codecdata (const GValue * streamheader, if (bufarr->len > 0) { for (bufi = 0; bufi < bufarr->len - 1; bufi++) { - for (i = 0; i < GST_BUFFER_SIZE (buf[bufi]) / 0xff; ++i) { + for (i = 0; i < gst_buffer_get_size (buf[bufi]) / 0xff; ++i) { priv_data[offset++] = 0xff; } - priv_data[offset++] = GST_BUFFER_SIZE (buf[bufi]) % 0xff; + priv_data[offset++] = gst_buffer_get_size (buf[bufi]) % 0xff; } } for (i = 0; i < bufarr->len; ++i) { - memcpy (priv_data + offset, GST_BUFFER_DATA (buf[i]), - GST_BUFFER_SIZE (buf[i])); - offset += GST_BUFFER_SIZE (buf[i]); + gst_buffer_extract (buf[i], 0, priv_data + offset, -1); + 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; @@ -1088,17 +1313,20 @@ vorbis_streamheader_to_codecdata (const GValue * streamheader, if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3)) return FALSE; - if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 4) { + if (buf0 == NULL || gst_buffer_get_size (buf0) < 1 + 6 + 4) { GST_WARNING ("First vorbis header too small, ignoring"); } else { - if (memcmp (GST_BUFFER_DATA (buf0) + 1, "vorbis", 6) == 0) { + if (gst_buffer_memcmp (buf0, 1, "vorbis", 6) == 0) { GstMatroskaTrackAudioContext *audiocontext; + GstMapInfo map; guint8 *hdr; - hdr = GST_BUFFER_DATA (buf0) + 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, &map); } } @@ -1117,16 +1345,18 @@ theora_streamheader_to_codecdata (const GValue * streamheader, if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3)) return FALSE; - if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 26) { + if (buf0 == NULL || gst_buffer_get_size (buf0) < 1 + 6 + 26) { GST_WARNING ("First theora header too small, ignoring"); - } else if (memcmp (GST_BUFFER_DATA (buf0), "\200theora\003\002", 9) != 0) { + } else if (gst_buffer_memcmp (buf0, 0, "\200theora\003\002", 9) != 0) { GST_WARNING ("First header not a theora identification header, ignoring"); } else { GstMatroskaTrackVideoContext *videocontext; guint fps_num, fps_denom, par_num, par_denom; + GstMapInfo map; guint8 *hdr; - hdr = GST_BUFFER_DATA (buf0) + 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; @@ -1157,6 +1387,8 @@ theora_streamheader_to_codecdata (const GValue * streamheader, videocontext->display_height = 0; } hdr += 3 + 3; + + gst_buffer_unmap (buf0, &map); } if (buf0) @@ -1174,9 +1406,9 @@ kate_streamheader_to_codecdata (const GValue * streamheader, if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, -1)) return FALSE; - if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 64) { /* Kate ID header is 64 bytes */ + if (buf0 == NULL || gst_buffer_get_size (buf0) < 64) { /* Kate ID header is 64 bytes */ GST_WARNING ("First kate header too small, ignoring"); - } else if (memcmp (GST_BUFFER_DATA (buf0), "\200kate\0\0\0", 8) != 0) { + } else if (gst_buffer_memcmp (buf0, 0, "\200kate\0\0\0", 8) != 0) { GST_WARNING ("First header not a kate identification header, ignoring"); } @@ -1217,38 +1449,37 @@ flac_streamheader_to_codecdata (const GValue * streamheader, buffer = g_value_peek_pointer (bufval); /* Need at least OggFLAC mapping header, fLaC marker and STREAMINFO block */ - if (GST_BUFFER_SIZE (buffer) < 9 + 4 + 4 + 34 - || memcmp (GST_BUFFER_DATA (buffer) + 1, "FLAC", 4) != 0 - || memcmp (GST_BUFFER_DATA (buffer) + 9, "fLaC", 4) != 0) { + if (gst_buffer_get_size (buffer) < 9 + 4 + 4 + 34 + || gst_buffer_memcmp (buffer, 1, "FLAC", 4) != 0 + || gst_buffer_memcmp (buffer, 9, "fLaC", 4) != 0) { GST_WARNING ("Invalid streamheader for FLAC"); return FALSE; } - context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer) - 9); - context->codec_priv_size = GST_BUFFER_SIZE (buffer) - 9; - memcpy (context->codec_priv, GST_BUFFER_DATA (buffer) + 9, - GST_BUFFER_SIZE (buffer) - 9); + 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); for (i = 1; i < bufarr->len; i++) { + guint old_size; 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; } buffer = g_value_peek_pointer (bufval); - context->codec_priv = - g_realloc (context->codec_priv, - context->codec_priv_size + GST_BUFFER_SIZE (buffer)); - memcpy ((guint8 *) context->codec_priv + context->codec_priv_size, - GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer)); - context->codec_priv_size = - context->codec_priv_size + GST_BUFFER_SIZE (buffer); + old_size = context->codec_priv_size; + context->codec_priv_size += gst_buffer_get_size (buffer); + + context->codec_priv = g_realloc (context->codec_priv, + context->codec_priv_size); + gst_buffer_extract (buffer, 0, + (guint8 *) context->codec_priv + old_size, -1); } return TRUE; @@ -1261,6 +1492,7 @@ speex_streamheader_to_codecdata (const GValue * streamheader, GArray *bufarr; GValue *bufval; GstBuffer *buffer; + guint old_size; if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) { GST_WARNING ("No or invalid streamheader field in the caps"); @@ -1283,51 +1515,47 @@ speex_streamheader_to_codecdata (const GValue * streamheader, buffer = g_value_peek_pointer (bufval); - if (GST_BUFFER_SIZE (buffer) < 80 - || memcmp (GST_BUFFER_DATA (buffer), "Speex ", 8) != 0) { + if (gst_buffer_get_size (buffer) < 80 + || gst_buffer_memcmp (buffer, 0, "Speex ", 8) != 0) { GST_WARNING ("Invalid streamheader for Speex"); return FALSE; } - context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer)); - context->codec_priv_size = GST_BUFFER_SIZE (buffer); - memcpy (context->codec_priv, GST_BUFFER_DATA (buffer), - GST_BUFFER_SIZE (buffer)); + gst_matroska_mux_free_codec_priv (context); + context->codec_priv_size = gst_buffer_get_size (buffer); + context->codec_priv = g_malloc (context->codec_priv_size); + gst_buffer_extract (buffer, 0, context->codec_priv, -1); bufval = &g_array_index (bufarr, GValue, 1); if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) { - g_free (context->codec_priv); - context->codec_priv = NULL; - context->codec_priv_size = 0; + gst_matroska_mux_free_codec_priv (context); GST_WARNING ("streamheaders array does not contain GstBuffers"); return FALSE; } buffer = g_value_peek_pointer (bufval); - context->codec_priv = - g_realloc (context->codec_priv, - context->codec_priv_size + GST_BUFFER_SIZE (buffer)); - memcpy ((guint8 *) context->codec_priv + context->codec_priv_size, - GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer)); - context->codec_priv_size = - context->codec_priv_size + GST_BUFFER_SIZE (buffer); + old_size = context->codec_priv_size; + context->codec_priv_size += gst_buffer_get_size (buffer); + context->codec_priv = g_realloc (context->codec_priv, + context->codec_priv_size); + gst_buffer_extract (buffer, 0, (guint8 *) context->codec_priv + old_size, -1); return TRUE; } static const gchar * -aac_codec_data_to_codec_id (const GstBuffer * buf) +aac_codec_data_to_codec_id (GstBuffer * buf) { const gchar *result; - gint profile; + guint8 profile; /* default to MAIN */ profile = 1; - if (GST_BUFFER_SIZE (buf) >= 2) { - profile = GST_READ_UINT8 (GST_BUFFER_DATA (buf)); + if (gst_buffer_get_size (buf) >= 2) { + gst_buffer_extract (buf, 0, &profile, 1); profile >>= 3; } @@ -1373,7 +1601,7 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) gint samplerate = 0, channels = 0; GstStructure *structure; const GValue *codec_data = NULL; - const GstBuffer *buf = NULL; + GstBuffer *buf = NULL; const gchar *stream_format = NULL; mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad)); @@ -1439,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; @@ -1485,62 +1716,53 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) default: goto refuse_caps; } - } else if (!strcmp (mimetype, "audio/x-raw-int")) { - gint width, depth; - gint endianness = G_LITTLE_ENDIAN; - gboolean signedness = TRUE; - - if (!gst_structure_get_int (structure, "width", &width) || - !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"); - goto refuse_caps; - } - - if (depth > 8 && - !gst_structure_get_int (structure, "endianness", &endianness)) { - GST_DEBUG_OBJECT (mux, "broken caps, no endianness specified"); - goto refuse_caps; - } - - if (width != depth) { - GST_DEBUG_OBJECT (mux, "width must be same as depth!"); - goto refuse_caps; - } + } else if (!strcmp (mimetype, "audio/x-raw")) { + GstAudioInfo info; - /* 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"); + gst_audio_info_init (&info); + if (!gst_audio_info_from_caps (&info, caps)) { + GST_DEBUG_OBJECT (mux, + "broken caps, rejected by gst_audio_info_from_caps"); goto refuse_caps; } - audiocontext->bitdepth = depth; - if (endianness == G_BIG_ENDIAN) - context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE); - else - context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE); - - } else if (!strcmp (mimetype, "audio/x-raw-float")) { - gint width; + switch (GST_AUDIO_INFO_FORMAT (&info)) { + case GST_AUDIO_FORMAT_U8: + case GST_AUDIO_FORMAT_S16BE: + case GST_AUDIO_FORMAT_S16LE: + case GST_AUDIO_FORMAT_S24BE: + case GST_AUDIO_FORMAT_S24LE: + case GST_AUDIO_FORMAT_S32BE: + case GST_AUDIO_FORMAT_S32LE: + if (GST_AUDIO_INFO_WIDTH (&info) != GST_AUDIO_INFO_DEPTH (&info)) { + GST_DEBUG_OBJECT (mux, "width must be same as depth!"); + goto refuse_caps; + } + if (GST_AUDIO_INFO_IS_BIG_ENDIAN (&info)) + gst_matroska_mux_set_codec_id (context, + GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE); + else + 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: + gst_matroska_mux_set_codec_id (context, + GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT); + break; - if (!gst_structure_get_int (structure, "width", &width)) { - GST_DEBUG_OBJECT (mux, "broken caps, width field missing"); - goto refuse_caps; + default: + GST_DEBUG_OBJECT (mux, "wrong format in raw audio caps"); + goto refuse_caps; } - audiocontext->bitdepth = width; - context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT); - + 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)) { @@ -1551,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)) { @@ -1567,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)) { @@ -1581,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; @@ -1594,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; @@ -1603,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; @@ -1622,54 +1840,76 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data); - priv_data_size = GST_BUFFER_SIZE (codec_data_buf); + priv_data_size = gst_buffer_get_size (codec_data_buf); priv_data = g_malloc0 (priv_data_size); - memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size); + 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; } - } else if (!strcmp (mimetype, "audio/x-wma")) { + } 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; + guint16 format = 0; gint block_align; gint bitrate; - 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) - || samplerate == 0 || channels == 0) { - GST_WARNING_OBJECT (mux, "Missing wmaversion/block_align/bitrate/" - "channels/rate on WMA caps"); + + if (samplerate == 0 || channels == 0) { + GST_WARNING_OBJECT (mux, "Missing channels/samplerate on 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); + 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; - } + } - if (gst_structure_get_int (structure, "depth", &depth)) - audiocontext->bitdepth = depth; + 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); + codec_priv_size += gst_buffer_get_size (buf); /* serialize waveformatex structure */ codec_priv = g_malloc0 (codec_priv_size); @@ -1680,17 +1920,18 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps) 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)); + GST_WRITE_UINT16_LE (codec_priv + 16, gst_buffer_get_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)); + gst_buffer_extract (buf, 0, + (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; } @@ -1706,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: @@ -1719,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 */ @@ -1734,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)); @@ -1753,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; } @@ -1791,97 +2076,94 @@ gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps) */ static GstPad * gst_matroska_mux_request_new_pad (GstElement * element, - GstPadTemplate * templ, const gchar * req_name) + GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps) { GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); GstMatroskaMux *mux = GST_MATROSKA_MUX (element); GstMatroskaPad *collect_pad; - GstPad *newpad = NULL; + GstMatroskamuxPad *newpad; gchar *name = NULL; const gchar *pad_name = NULL; - GstPadSetCapsFunction setcapsfunc = NULL; + 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_%d")) { + 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_%d", &pad_id) == 1) { + if (req_name != NULL && sscanf (req_name, "audio_%u", &pad_id) == 1) { pad_name = req_name; } else { - name = g_strdup_printf ("audio_%d", mux->num_a_streams++); + name = g_strdup_printf ("audio_%u", mux->num_a_streams++); pad_name = name; } - setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_audio_pad_setcaps); + capsfunc = 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")) { + } 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_%d", &pad_id) == 1) { + if (req_name != NULL && sscanf (req_name, "video_%u", &pad_id) == 1) { pad_name = req_name; } else { - name = g_strdup_printf ("video_%d", mux->num_v_streams++); + name = g_strdup_printf ("video_%u", mux->num_v_streams++); pad_name = name; } - setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_video_pad_setcaps); + capsfunc = 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")) { + } 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_%d", &pad_id) == 1) { + if (req_name != NULL && sscanf (req_name, "subtitle_%u", &pad_id) == 1) { pad_name = req_name; } else { - name = g_strdup_printf ("subtitle_%d", mux->num_t_streams++); + name = g_strdup_printf ("subtitle_%u", mux->num_t_streams++); pad_name = name; } - setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_subtitle_pad_setcaps); + capsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_subtitle_pad_setcaps); context = (GstMatroskaTrackContext *) 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; } - newpad = gst_pad_new_from_template (templ, pad_name); + newpad = g_object_new (GST_TYPE_MATROSKAMUX_PAD, + "name", pad_name, "direction", templ->direction, "template", templ, NULL); g_free (name); + + gst_matroskamux_pad_init (newpad); collect_pad = (GstMatroskaPad *) - gst_collect_pads_add_pad_full (mux->collect, 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); + collect_pad->track->codec_id = id; - /* 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 (newpad, - GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_sink_event)); - - gst_pad_set_setcaps_function (newpad, setcapsfunc); - gst_pad_set_active (newpad, TRUE); - if (!gst_element_add_pad (element, newpad)) + collect_pad->capsfunc = capsfunc; + gst_pad_set_active (GST_PAD (newpad), TRUE); + if (!gst_element_add_pad (element, GST_PAD (newpad))) goto pad_add_failed; mux->num_streams++; GST_DEBUG_OBJECT (newpad, "Added new request pad"); - return newpad; + return GST_PAD (newpad); /* ERROR cases */ pad_add_failed: @@ -1908,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) { @@ -1929,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--; } @@ -1966,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:{ @@ -2016,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; @@ -2025,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: @@ -2048,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 @@ -2060,10 +2466,34 @@ 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_simple ("video/webm", NULL); + ebml->caps = gst_caps_new_empty_simple ("video/webm"); } else { - ebml->caps = gst_caps_new_simple ("video/x-matroska", NULL); + ebml->caps = gst_caps_new_empty_simple ("video/x-matroska"); } /* we start with a EBML header */ doctype = mux->doctype; @@ -2092,6 +2522,27 @@ gst_matroska_mux_start (GstMatroskaMux * mux) 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_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); + } + } + /* segment info */ mux->info_pos = ebml->pos; master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENTINFO); @@ -2107,7 +2558,6 @@ gst_matroska_mux_start (GstMatroskaMux * mux) for (collected = mux->collect->data; collected; collected = g_slist_next (collected)) { GstMatroskaPad *collect_pad; - GstFormat format = GST_FORMAT_TIME; GstPad *thepad; gint64 trackduration; @@ -2116,7 +2566,7 @@ gst_matroska_mux_start (GstMatroskaMux * mux) /* Query the total length of the track. */ GST_DEBUG_OBJECT (thepad, "querying peer duration"); - if (gst_pad_query_peer_duration (thepad, &format, &trackduration)) { + if (gst_pad_peer_query_duration (thepad, GST_FORMAT_TIME, &trackduration)) { GST_DEBUG_OBJECT (thepad, "duration: %" GST_TIME_FORMAT, GST_TIME_ARGS (trackduration)); if (trackduration != GST_CLOCK_TIME_NONE && trackduration > duration) { @@ -2155,10 +2605,76 @@ 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 remaining pad/track setup */ + collect_pad->default_duration_scaled = + gst_util_uint64_scale (collect_pad->track->default_duration, + 1, mux->time_scale); } } gst_ebml_write_master_finish (ebml, master); + /* chapters */ + if (gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL && !mux->streamable) { + guint64 master_chapters = 0; + GstTocEntry *toc_entry; + const GstToc *toc; + GList *cur, *to_write = NULL; + gint64 start, stop; + + GST_DEBUG ("Writing chapters"); + + toc = gst_toc_setter_get_toc (GST_TOC_SETTER (mux)); + + /* check whether we have editions or chapters at the root level */ + toc_entry = toc->entries->data; + + if (toc_entry->type != GST_TOC_ENTRY_TYPE_EDITION) { + toc_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, ""); + gst_toc_entry_set_start_stop (toc_entry, -1, -1); + + /* aggregate all chapters without root edition */ + cur = toc->entries; + while (cur != NULL) { + toc_entry->subentries = + g_list_prepend (toc_entry->subentries, cur->data); + cur = cur->next; + } + + gst_toc_entry_get_start_stop (((GstTocEntry *) toc_entry-> + subentries->data), &start, NULL); + toc_entry->subentries = g_list_reverse (toc_entry->subentries); + gst_toc_entry_get_start_stop (((GstTocEntry *) toc_entry-> + subentries->data), NULL, &stop); + gst_toc_entry_set_start_stop (toc_entry, start, stop); + + to_write = g_list_append (to_write, toc_entry); + } else { + toc_entry = NULL; + to_write = toc->entries; + } + + /* finally write chapters */ + mux->chapters_pos = ebml->pos; + + cur = to_write; + while (cur != NULL) { + gst_matroska_mux_write_chapter_edition (mux, cur->data, ebml, + &master_chapters); + cur = cur->next; + } + + /* close master element if any edition was written */ + if (G_LIKELY (master_chapters != 0)) + gst_ebml_write_master_finish (ebml, master_chapters); + + if (toc_entry != NULL) { + g_list_free (toc_entry->subentries); + toc_entry->subentries = NULL; + gst_toc_entry_free (toc_entry); + g_list_free (to_write); + } + } + /* lastly, flush the cache */ gst_ebml_write_flush_cache (ebml, FALSE, 0); } @@ -2168,7 +2684,7 @@ gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag, gpointer data) { /* TODO: more sensible tag mappings */ - struct + static const struct { const gchar *matroska_tagname; const gchar *gstreamer_tagname; @@ -2176,7 +2692,7 @@ gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag, 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}, { @@ -2222,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: @@ -2275,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, @@ -2303,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; @@ -2314,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; @@ -2326,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 @@ -2357,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)); @@ -2380,52 +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) - *popped = TRUE; - } - - /* 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. @@ -2441,15 +3026,16 @@ gst_matroska_mux_create_buffer_header (GstMatroskaTrackContext * track, gint16 relative_timestamp, int flags) { GstBuffer *hdr; + guint8 *data = g_malloc (4); - hdr = gst_buffer_new_and_alloc (4); + hdr = gst_buffer_new_wrapped (data, 4); /* track num - FIXME: what if num >= 0x80 (unlikely)? */ - GST_BUFFER_DATA (hdr)[0] = track->num | 0x80; + data[0] = track->num | 0x80; /* time relative to clustertime */ - GST_WRITE_UINT16_BE (GST_BUFFER_DATA (hdr) + 1, relative_timestamp); + GST_WRITE_UINT16_BE (data + 1, relative_timestamp); /* flags */ - GST_BUFFER_DATA (hdr)[3] = flags; + data[3] = flags; return hdr; } @@ -2464,14 +3050,20 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux, { GstMatroskaTrackVideoContext *ctx = (GstMatroskaTrackVideoContext *) collect_pad->track; - const guint8 *data = GST_BUFFER_DATA (buf); - guint size = GST_BUFFER_SIZE (buf); + GstMapInfo map; + guint8 *data; + gsize size; guint8 parse_code; guint32 next_parse_offset; GstBuffer *ret = NULL; gboolean is_muxing_unit = FALSE; - if (GST_BUFFER_SIZE (buf) < 13) { + gst_buffer_map (buf, &map, GST_MAP_READ); + data = map.data; + size = map.size; + + if (size < 13) { + gst_buffer_unmap (buf, &map); gst_buffer_unref (buf); return ret; } @@ -2479,6 +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, &map); gst_buffer_unref (buf); return ret; } @@ -2497,7 +3090,7 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux, next_parse_offset = GST_READ_UINT32_BE (data + 5); - if (G_UNLIKELY (next_parse_offset == 0)) + if (G_UNLIKELY (next_parse_offset == 0 || next_parse_offset > size)) break; data += next_parse_offset; @@ -2505,16 +3098,17 @@ 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, &map); + if (is_muxing_unit) { - ret = gst_buffer_make_metadata_writable (ctx->dirac_unit); + ret = gst_buffer_make_writable (ctx->dirac_unit); ctx->dirac_unit = NULL; - gst_buffer_copy_metadata (ret, buf, - GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS | - GST_BUFFER_COPY_CAPS); + gst_buffer_copy_into (ret, buf, + GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1); gst_buffer_unref (buf); } else { gst_buffer_unref (buf); @@ -2536,14 +3130,14 @@ gst_matroska_mux_stop_streamheader (GstMatroskaMux * mux) streamheader_buffer = gst_ebml_stop_streamheader (ebml); if (!strcmp (mux->doctype, GST_MATROSKA_DOCTYPE_WEBM)) { - caps = gst_caps_new_simple ("video/webm", NULL); + caps = gst_caps_new_empty_simple ("video/webm"); } else { - caps = gst_caps_new_simple ("video/x-matroska", NULL); + caps = gst_caps_new_empty_simple ("video/x-matroska"); } 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); @@ -2564,20 +3158,21 @@ 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; gint64 relative_timestamp64; guint64 block_duration; gboolean is_video_keyframe = FALSE; + 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 */ if (collect_pad->track->xiph_headers_to_skip > 0) { @@ -2618,13 +3213,20 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) } if (mux->cluster) { - /* start a new cluster at every keyframe or when we may be reaching the - * limit of the relative timestamp */ + /* 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) { + || 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; + } + mux->prev_cluster_size = ebml->pos - mux->cluster_pos; mux->cluster_pos = ebml->pos; gst_ebml_write_set_cache (ebml, 0x20); @@ -2665,9 +3267,10 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) * the block in the cluster which contains the timestamp, should also work * for files with multiple audio tracks. */ - if (is_video_keyframe || - ((collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_AUDIO) && - (mux->num_streams == 1))) { + 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) { @@ -2696,9 +3299,14 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) /* Check if the duration differs from the default duration. */ write_duration = FALSE; - block_duration = GST_BUFFER_DURATION (buf); - if (GST_BUFFER_DURATION_IS_VALID (buf)) { - if (block_duration != collect_pad->track->default_duration) { + block_duration = 0; + if (pad->frame_duration && GST_BUFFER_DURATION_IS_VALID (buf)) { + 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; } } @@ -2725,28 +3333,27 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad) 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_buffer_get_size (buf) + gst_buffer_get_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); + gst_ebml_write_set_cache (ebml, gst_buffer_get_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, - gst_util_uint64_scale (block_duration, 1, mux->time_scale)); - } + 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_buffer_get_size (buf) + gst_buffer_get_size (hdr)); gst_ebml_write_buffer (ebml, hdr); - gst_ebml_write_master_finish_full (ebml, blockgroup, GST_BUFFER_SIZE (buf)); + gst_ebml_write_master_finish_full (ebml, blockgroup, + gst_buffer_get_size (buf)); gst_ebml_write_flush_cache (ebml, FALSE, GST_BUFFER_TIMESTAMP (buf)); gst_ebml_write_buffer (ebml, buf); @@ -2754,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. @@ -2765,13 +3371,13 @@ 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; + GstFlowReturn ret = GST_FLOW_OK; GST_DEBUG_OBJECT (mux, "Collected pads"); @@ -2789,50 +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) { - 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; } @@ -2856,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;