1 /* GStreamer Matroska muxer/demuxer
2 * (c) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
3 * (c) 2005 Michal Benes <michal.benes@xeris.cz>
4 * (c) 2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>
6 * matroska-mux.c: matroska file/stream muxer
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public
19 * License along with this library; if not, write to the
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
24 /* TODO: - check everywhere that we don't write invalid values
25 * - make sure timestamps are correctly scaled everywhere
29 * SECTION:element-matroskamux
31 * matroskamux muxes different input streams into a Matroska file.
34 * <title>Example launch line</title>
36 * gst-launch -v filesrc location=/path/to/mp3 ! mp3parse ! matroskamux name=mux ! filesink location=test.mkv filesrc location=/path/to/theora.ogg ! oggdemux ! theoraparse ! mux.
37 * ]| This pipeline muxes an MP3 file and a Ogg Theora video into a Matroska file.
39 * gst-launch -v audiotestsrc num-buffers=100 ! audioconvert ! vorbisenc ! matroskamux ! filesink location=test.mka
40 * ]| This pipeline muxes a 440Hz sine wave encoded with the Vorbis codec into a Matroska file.
51 #include <gst/riff/riff-media.h>
52 #include <gst/tag/tag.h>
54 #include "matroska-mux.h"
55 #include "matroska-ids.h"
57 GST_DEBUG_CATEGORY_STATIC (matroskamux_debug);
58 #define GST_CAT_DEFAULT matroskamux_debug
65 ARG_MIN_INDEX_INTERVAL
68 #define DEFAULT_MATROSKA_VERSION 1
69 #define DEFAULT_WRITING_APP "GStreamer Matroska muxer"
70 #define DEFAULT_MIN_INDEX_INTERVAL 0
72 static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
75 GST_STATIC_CAPS ("video/x-matroska")
78 #define COMMON_VIDEO_CAPS \
79 "width = (int) [ 16, 4096 ], " \
80 "height = (int) [ 16, 4096 ], " \
81 "framerate = (fraction) [ 0, MAX ]"
83 #define COMMON_VIDEO_CAPS_NO_FRAMERATE \
84 "width = (int) [ 16, 4096 ], " \
85 "height = (int) [ 16, 4096 ] "
88 * * require codec data, etc as needed
91 static GstStaticPadTemplate videosink_templ =
92 GST_STATIC_PAD_TEMPLATE ("video_%d",
95 GST_STATIC_CAPS ("video/mpeg, "
96 "mpegversion = (int) { 1, 2, 4 }, "
97 "systemstream = (boolean) false, "
98 COMMON_VIDEO_CAPS "; "
100 COMMON_VIDEO_CAPS "; "
102 COMMON_VIDEO_CAPS "; "
104 COMMON_VIDEO_CAPS "; "
106 COMMON_VIDEO_CAPS "; "
108 COMMON_VIDEO_CAPS "; "
110 COMMON_VIDEO_CAPS "; "
112 COMMON_VIDEO_CAPS "; "
114 COMMON_VIDEO_CAPS_NO_FRAMERATE "; "
117 COMMON_VIDEO_CAPS "; "
118 "video/x-pn-realvideo, "
119 "rmversion = (int) [1, 4], "
120 COMMON_VIDEO_CAPS "; "
122 "format = (fourcc) { YUY2, I420, YV12, UYVY, AYUV }, "
123 COMMON_VIDEO_CAPS "; "
124 "video/x-wmv, " "wmvversion = (int) [ 1, 3 ], " COMMON_VIDEO_CAPS)
127 #define COMMON_AUDIO_CAPS \
128 "channels = (int) [ 1, MAX ], " \
129 "rate = (int) [ 1, MAX ]"
132 * * require codec data, etc as needed
134 static GstStaticPadTemplate audiosink_templ =
135 GST_STATIC_PAD_TEMPLATE ("audio_%d",
138 GST_STATIC_CAPS ("audio/mpeg, "
139 "mpegversion = (int) 1, "
140 "layer = (int) [ 1, 3 ], "
141 "stream-format = (string) { raw }, "
142 COMMON_AUDIO_CAPS "; "
144 "mpegversion = (int) { 2, 4 }, "
145 COMMON_AUDIO_CAPS "; "
147 COMMON_AUDIO_CAPS "; "
149 COMMON_AUDIO_CAPS "; "
151 COMMON_AUDIO_CAPS "; "
153 COMMON_AUDIO_CAPS "; "
157 "signed = (boolean) false, "
158 COMMON_AUDIO_CAPS ";"
162 "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, "
163 "signed = (boolean) true, "
164 COMMON_AUDIO_CAPS ";"
168 "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, "
169 "signed = (boolean) true, "
170 COMMON_AUDIO_CAPS ";"
174 "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, "
175 "signed = (boolean) true, "
176 COMMON_AUDIO_CAPS ";"
177 "audio/x-raw-float, "
178 "width = (int) [ 32, 64 ], "
179 "endianness = (int) LITTLE_ENDIAN, "
180 COMMON_AUDIO_CAPS ";"
182 "width = (int) { 8, 16, 24 }, "
183 "channels = (int) { 1, 2 }, " "rate = (int) [ 8000, 96000 ]; "
184 "audio/x-pn-realaudio, "
185 "raversion = (int) { 1, 2, 8 }, " COMMON_AUDIO_CAPS "; "
186 "audio/x-wma, " "wmaversion = (int) [ 1, 3 ], "
187 "block_align = (int) [ 0, 65535 ], bitrate = (int) [ 0, 524288 ], "
191 static GstStaticPadTemplate subtitlesink_templ =
192 GST_STATIC_PAD_TEMPLATE ("subtitle_%d",
195 GST_STATIC_CAPS_ANY);
197 static GArray *used_uids;
198 G_LOCK_DEFINE_STATIC (used_uids);
200 static void gst_matroska_mux_add_interfaces (GType type);
202 GST_BOILERPLATE_FULL (GstMatroskaMux, gst_matroska_mux, GstElement,
203 GST_TYPE_ELEMENT, gst_matroska_mux_add_interfaces);
205 /* Matroska muxer destructor */
206 static void gst_matroska_mux_finalize (GObject * object);
208 /* Pads collected callback */
210 gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data);
213 static gboolean gst_matroska_mux_handle_src_event (GstPad * pad,
215 static GstPad *gst_matroska_mux_request_new_pad (GstElement * element,
216 GstPadTemplate * templ, const gchar * name);
217 static void gst_matroska_mux_release_pad (GstElement * element, GstPad * pad);
219 /* gst internal change state handler */
220 static GstStateChangeReturn
221 gst_matroska_mux_change_state (GstElement * element, GstStateChange transition);
223 /* gobject bla bla */
224 static void gst_matroska_mux_set_property (GObject * object,
225 guint prop_id, const GValue * value, GParamSpec * pspec);
226 static void gst_matroska_mux_get_property (GObject * object,
227 guint prop_id, GValue * value, GParamSpec * pspec);
230 static void gst_matroska_mux_reset (GstElement * element);
233 static guint64 gst_matroska_mux_create_uid ();
235 static gboolean theora_streamheader_to_codecdata (const GValue * streamheader,
236 GstMatroskaTrackContext * context);
237 static gboolean vorbis_streamheader_to_codecdata (const GValue * streamheader,
238 GstMatroskaTrackContext * context);
239 static gboolean speex_streamheader_to_codecdata (const GValue * streamheader,
240 GstMatroskaTrackContext * context);
241 static gboolean kate_streamheader_to_codecdata (const GValue * streamheader,
242 GstMatroskaTrackContext * context);
243 static gboolean flac_streamheader_to_codecdata (const GValue * streamheader,
244 GstMatroskaTrackContext * context);
247 gst_matroska_mux_add_interfaces (GType type)
249 static const GInterfaceInfo tag_setter_info = { NULL, NULL, NULL };
251 g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
255 gst_matroska_mux_base_init (gpointer g_class)
257 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
259 gst_element_class_add_pad_template (element_class,
260 gst_static_pad_template_get (&videosink_templ));
261 gst_element_class_add_pad_template (element_class,
262 gst_static_pad_template_get (&audiosink_templ));
263 gst_element_class_add_pad_template (element_class,
264 gst_static_pad_template_get (&subtitlesink_templ));
265 gst_element_class_add_pad_template (element_class,
266 gst_static_pad_template_get (&src_templ));
267 gst_element_class_set_details_simple (element_class, "Matroska muxer",
269 "Muxes video/audio/subtitle streams into a matroska stream",
270 "GStreamer maintainers <gstreamer-devel@lists.sourceforge.net>");
272 GST_DEBUG_CATEGORY_INIT (matroskamux_debug, "matroskamux", 0,
277 gst_matroska_mux_class_init (GstMatroskaMuxClass * klass)
279 GObjectClass *gobject_class;
280 GstElementClass *gstelement_class;
282 gobject_class = (GObjectClass *) klass;
283 gstelement_class = (GstElementClass *) klass;
285 gobject_class->finalize = gst_matroska_mux_finalize;
287 gobject_class->get_property = gst_matroska_mux_get_property;
288 gobject_class->set_property = gst_matroska_mux_set_property;
290 g_object_class_install_property (gobject_class, ARG_WRITING_APP,
291 g_param_spec_string ("writing-app", "Writing application.",
292 "The name the application that creates the matroska file.",
293 NULL, G_PARAM_READWRITE));
294 g_object_class_install_property (gobject_class, ARG_MATROSKA_VERSION,
295 g_param_spec_int ("version", "Matroska version",
296 "This parameter determines what matroska features can be used.",
297 1, 2, DEFAULT_MATROSKA_VERSION, G_PARAM_READWRITE));
298 g_object_class_install_property (gobject_class, ARG_MIN_INDEX_INTERVAL,
299 g_param_spec_int64 ("min-index-interval", "Minimum time between index "
300 "entries", "An index entry is created every so many nanoseconds.",
301 0, G_MAXINT64, DEFAULT_MIN_INDEX_INTERVAL, G_PARAM_READWRITE));
303 gstelement_class->change_state =
304 GST_DEBUG_FUNCPTR (gst_matroska_mux_change_state);
305 gstelement_class->request_new_pad =
306 GST_DEBUG_FUNCPTR (gst_matroska_mux_request_new_pad);
307 gstelement_class->release_pad =
308 GST_DEBUG_FUNCPTR (gst_matroska_mux_release_pad);
313 * gst_matroska_mux_init:
314 * @mux: #GstMatroskaMux that should be initialized.
315 * @g_class: Class of the muxer.
317 * Matroska muxer constructor.
320 gst_matroska_mux_init (GstMatroskaMux * mux, GstMatroskaMuxClass * g_class)
322 mux->srcpad = gst_pad_new_from_static_template (&src_templ, "src");
323 gst_pad_set_event_function (mux->srcpad, gst_matroska_mux_handle_src_event);
324 gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad);
326 mux->collect = gst_collect_pads_new ();
327 gst_collect_pads_set_function (mux->collect,
328 (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_matroska_mux_collected),
331 mux->ebml_write = gst_ebml_write_new (mux->srcpad);
333 /* property defaults */
334 mux->matroska_version = DEFAULT_MATROSKA_VERSION;
335 mux->writing_app = g_strdup (DEFAULT_WRITING_APP);
336 mux->min_index_interval = DEFAULT_MIN_INDEX_INTERVAL;
338 /* initialize internal variables */
340 mux->num_streams = 0;
341 mux->num_a_streams = 0;
342 mux->num_t_streams = 0;
343 mux->num_v_streams = 0;
345 /* initialize remaining variables */
346 gst_matroska_mux_reset (GST_ELEMENT (mux));
351 * gst_matroska_mux_finalize:
352 * @object: #GstMatroskaMux that should be finalized.
354 * Finalize matroska muxer.
357 gst_matroska_mux_finalize (GObject * object)
359 GstMatroskaMux *mux = GST_MATROSKA_MUX (object);
361 gst_object_unref (mux->collect);
362 gst_object_unref (mux->ebml_write);
363 if (mux->writing_app)
364 g_free (mux->writing_app);
366 G_OBJECT_CLASS (parent_class)->finalize (object);
371 * gst_matroska_mux_create_uid:
373 * Generate new unused track UID.
375 * Returns: New track UID.
378 gst_matroska_mux_create_uid (void)
385 used_uids = g_array_sized_new (FALSE, FALSE, sizeof (guint64), 10);
390 uid = (((guint64) g_random_int ()) << 32) | g_random_int ();
391 for (i = 0; i < used_uids->len; i++) {
392 if (g_array_index (used_uids, guint64, i) == uid) {
397 g_array_append_val (used_uids, uid);
400 G_UNLOCK (used_uids);
406 * gst_matroska_pad_reset:
407 * @collect_pad: the #GstMatroskaPad
409 * Reset and/or release resources of a matroska collect pad.
412 gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full)
415 GstMatroskaTrackType type = 0;
417 /* free track information */
418 if (collect_pad->track != NULL) {
419 /* retrieve for optional later use */
420 name = collect_pad->track->name;
421 type = collect_pad->track->type;
422 /* extra for video */
423 if (type == GST_MATROSKA_TRACK_TYPE_VIDEO) {
424 GstMatroskaTrackVideoContext *ctx =
425 (GstMatroskaTrackVideoContext *) collect_pad->track;
427 if (ctx->dirac_unit) {
428 gst_buffer_unref (ctx->dirac_unit);
429 ctx->dirac_unit = NULL;
432 g_free (collect_pad->track->codec_id);
433 g_free (collect_pad->track->codec_name);
435 g_free (collect_pad->track->name);
436 g_free (collect_pad->track->language);
437 g_free (collect_pad->track->codec_priv);
438 g_free (collect_pad->track);
439 collect_pad->track = NULL;
442 /* free cached buffer */
443 if (collect_pad->buffer != NULL) {
444 gst_buffer_unref (collect_pad->buffer);
445 collect_pad->buffer = NULL;
448 if (!full && type != 0) {
449 GstMatroskaTrackContext *context;
451 /* create a fresh context */
453 case GST_MATROSKA_TRACK_TYPE_VIDEO:
454 context = (GstMatroskaTrackContext *)
455 g_new0 (GstMatroskaTrackVideoContext, 1);
457 case GST_MATROSKA_TRACK_TYPE_AUDIO:
458 context = (GstMatroskaTrackContext *)
459 g_new0 (GstMatroskaTrackAudioContext, 1);
461 case GST_MATROSKA_TRACK_TYPE_SUBTITLE:
462 context = (GstMatroskaTrackContext *)
463 g_new0 (GstMatroskaTrackSubtitleContext, 1);
466 g_assert_not_reached ();
470 context->type = type;
471 context->name = name;
472 /* TODO: check default values for the context */
473 context->flags = GST_MATROSKA_TRACK_ENABLED | GST_MATROSKA_TRACK_DEFAULT;
474 collect_pad->track = context;
475 collect_pad->buffer = NULL;
476 collect_pad->duration = 0;
477 collect_pad->start_ts = GST_CLOCK_TIME_NONE;
478 collect_pad->end_ts = GST_CLOCK_TIME_NONE;
483 * gst_matroska_pad_free:
484 * @collect_pad: the #GstMatroskaPad
486 * Release resources of a matroska collect pad.
489 gst_matroska_pad_free (GstMatroskaPad * collect_pad)
491 gst_matroska_pad_reset (collect_pad, TRUE);
496 * gst_matroska_mux_reset:
497 * @element: #GstMatroskaMux that should be reseted.
499 * Reset matroska muxer back to initial state.
502 gst_matroska_mux_reset (GstElement * element)
504 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
507 /* reset EBML write */
508 gst_ebml_write_reset (mux->ebml_write);
511 mux->state = GST_MATROSKA_MUX_STATE_START;
513 /* clean up existing streams */
515 for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
516 GstMatroskaPad *collect_pad;
518 collect_pad = (GstMatroskaPad *) walk->data;
520 /* reset collect pad to pristine state */
521 gst_matroska_pad_reset (collect_pad, FALSE);
525 mux->num_indexes = 0;
530 mux->time_scale = GST_MSECOND;
535 mux->cluster_time = 0;
536 mux->cluster_pos = 0;
539 gst_tag_setter_reset_tags (GST_TAG_SETTER (mux));
543 * gst_matroska_mux_handle_src_event:
544 * @pad: Pad which received the event.
545 * @event: Received event.
547 * handle events - copied from oggmux without understanding
549 * Returns: #TRUE on success.
552 gst_matroska_mux_handle_src_event (GstPad * pad, GstEvent * event)
556 type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;
560 /* disable seeking for now */
566 return gst_pad_event_default (pad, event);
570 * gst_matroska_mux_handle_sink_event:
571 * @pad: Pad which received the event.
572 * @event: Received event.
574 * handle events - informational ones like tags
576 * Returns: #TRUE on success.
579 gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event)
581 GstMatroskaTrackContext *context;
582 GstMatroskaPad *collect_pad;
587 mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad));
589 /* FIXME: aren't we either leaking events here or doing a wrong unref? */
590 switch (GST_EVENT_TYPE (event)) {
594 GST_DEBUG_OBJECT (mux, "received tag event");
595 gst_event_parse_tag (event, &list);
597 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
598 g_assert (collect_pad);
599 context = collect_pad->track;
602 /* Matroska wants ISO 639-2B code, taglist most likely contains 639-1 */
603 if (gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &lang)) {
604 const gchar *lang_code;
606 lang_code = gst_tag_get_language_code_iso_639_2B (lang);
608 GST_INFO_OBJECT (pad, "Setting language to '%s'", lang_code);
609 context->language = g_strdup (lang_code);
611 GST_WARNING_OBJECT (pad, "Did not get language code for '%s'", lang);
616 gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), list,
617 gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (mux)));
620 case GST_EVENT_NEWSEGMENT:
621 /* We don't support NEWSEGMENT events */
623 gst_event_unref (event);
629 /* now GstCollectPads can take care of the rest, e.g. EOS */
631 ret = mux->collect_event (pad, event);
632 gst_object_unref (mux);
639 * gst_matroska_mux_video_pad_setcaps:
640 * @pad: Pad which got the caps.
643 * Setcaps function for video sink pad.
645 * Returns: #TRUE on success.
648 gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
650 GstMatroskaTrackContext *context = NULL;
651 GstMatroskaTrackVideoContext *videocontext;
653 GstMatroskaPad *collect_pad;
654 GstStructure *structure;
655 const gchar *mimetype;
656 const GValue *value = NULL;
657 const GstBuffer *codec_buf = NULL;
658 gint width, height, pixel_width, pixel_height;
661 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
664 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
665 g_assert (collect_pad);
666 context = collect_pad->track;
668 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO);
669 videocontext = (GstMatroskaTrackVideoContext *) context;
671 /* gst -> matroska ID'ing */
672 structure = gst_caps_get_structure (caps, 0);
674 mimetype = gst_structure_get_name (structure);
676 if (!strcmp (mimetype, "video/x-theora")) {
677 /* we'll extract the details later from the theora identification header */
681 /* get general properties */
682 /* spec says it is mandatory */
683 if (!gst_structure_get_int (structure, "width", &width) ||
684 !gst_structure_get_int (structure, "height", &height))
687 videocontext->pixel_width = width;
688 videocontext->pixel_height = height;
689 if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d)
691 context->default_duration =
692 gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n);
693 GST_LOG_OBJECT (pad, "default duration = %" GST_TIME_FORMAT,
694 GST_TIME_ARGS (context->default_duration));
696 context->default_duration = 0;
698 if (gst_structure_get_fraction (structure, "pixel-aspect-ratio",
699 &pixel_width, &pixel_height)) {
700 if (pixel_width > pixel_height) {
701 videocontext->display_width = width * pixel_width / pixel_height;
702 videocontext->display_height = height;
703 } else if (pixel_width < pixel_height) {
704 videocontext->display_width = width;
705 videocontext->display_height = height * pixel_height / pixel_width;
707 videocontext->display_width = 0;
708 videocontext->display_height = 0;
711 videocontext->display_width = 0;
712 videocontext->display_height = 0;
717 videocontext->asr_mode = GST_MATROSKA_ASPECT_RATIO_MODE_FREE;
718 videocontext->fourcc = 0;
720 /* TODO: - check if we handle all codecs by the spec, i.e. codec private
721 * data and other settings
725 /* extract codec_data, may turn out needed */
726 value = gst_structure_get_value (structure, "codec_data");
728 codec_buf = gst_value_get_buffer (value);
731 if (!strcmp (mimetype, "video/x-raw-yuv")) {
732 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED);
733 gst_structure_get_fourcc (structure, "format", &videocontext->fourcc);
734 } else if (!strcmp (mimetype, "image/jpeg")) {
735 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
736 } else if (!strcmp (mimetype, "video/x-xvid") /* MS/VfW compatibility cases */
737 ||!strcmp (mimetype, "video/x-huffyuv")
738 || !strcmp (mimetype, "video/x-divx")
739 || !strcmp (mimetype, "video/x-dv")
740 || !strcmp (mimetype, "video/x-h263")
741 || !strcmp (mimetype, "video/x-msmpeg")
742 || !strcmp (mimetype, "video/x-wmv")) {
743 BITMAPINFOHEADER *bih;
744 gint size = sizeof (BITMAPINFOHEADER);
747 if (!strcmp (mimetype, "video/x-xvid"))
748 fourcc = GST_MAKE_FOURCC ('X', 'V', 'I', 'D');
749 else if (!strcmp (mimetype, "video/x-huffyuv"))
750 fourcc = GST_MAKE_FOURCC ('H', 'F', 'Y', 'U');
751 else if (!strcmp (mimetype, "video/x-dv"))
752 fourcc = GST_MAKE_FOURCC ('D', 'V', 'S', 'D');
753 else if (!strcmp (mimetype, "video/x-h263"))
754 fourcc = GST_MAKE_FOURCC ('H', '2', '6', '3');
755 else if (!strcmp (mimetype, "video/x-divx")) {
758 gst_structure_get_int (structure, "divxversion", &divxversion);
759 switch (divxversion) {
761 fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', '3');
764 fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', 'X');
767 fourcc = GST_MAKE_FOURCC ('D', 'X', '5', '0');
770 } else if (!strcmp (mimetype, "video/x-msmpeg")) {
773 gst_structure_get_int (structure, "msmpegversion", &msmpegversion);
774 switch (msmpegversion) {
776 fourcc = GST_MAKE_FOURCC ('M', 'P', 'G', '4');
779 fourcc = GST_MAKE_FOURCC ('M', 'P', '4', '2');
785 } else if (!strcmp (mimetype, "video/x-wmv")) {
788 if (gst_structure_get_fourcc (structure, "format", &format)) {
790 } else if (gst_structure_get_int (structure, "wmvversion", &wmvversion)) {
791 if (wmvversion == 2) {
792 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '2');
793 } else if (wmvversion == 1) {
794 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '1');
795 } else if (wmvversion == 3) {
796 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '3');
804 bih = g_new0 (BITMAPINFOHEADER, 1);
805 GST_WRITE_UINT32_LE (&bih->bi_size, size);
806 GST_WRITE_UINT32_LE (&bih->bi_width, videocontext->pixel_width);
807 GST_WRITE_UINT32_LE (&bih->bi_height, videocontext->pixel_height);
808 GST_WRITE_UINT32_LE (&bih->bi_compression, fourcc);
809 GST_WRITE_UINT16_LE (&bih->bi_planes, (guint16) 1);
810 GST_WRITE_UINT16_LE (&bih->bi_bit_count, (guint16) 24);
811 GST_WRITE_UINT32_LE (&bih->bi_size_image, videocontext->pixel_width *
812 videocontext->pixel_height * 3);
814 /* process codec private/initialization data, if any */
816 size += GST_BUFFER_SIZE (codec_buf);
817 bih = g_realloc (bih, size);
818 GST_WRITE_UINT32_LE (&bih->bi_size, size);
819 memcpy ((guint8 *) bih + sizeof (BITMAPINFOHEADER),
820 GST_BUFFER_DATA (codec_buf), GST_BUFFER_SIZE (codec_buf));
823 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
824 context->codec_priv = (gpointer) bih;
825 context->codec_priv_size = size;
826 } else if (!strcmp (mimetype, "video/x-h264")) {
827 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC);
829 if (context->codec_priv != NULL) {
830 g_free (context->codec_priv);
831 context->codec_priv = NULL;
832 context->codec_priv_size = 0;
835 /* Create avcC header */
836 if (codec_buf != NULL) {
837 context->codec_priv_size = GST_BUFFER_SIZE (codec_buf);
838 context->codec_priv = g_malloc0 (context->codec_priv_size);
839 memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
840 context->codec_priv_size);
842 } else if (!strcmp (mimetype, "video/x-theora")) {
843 const GValue *streamheader;
845 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_THEORA);
847 if (context->codec_priv != NULL) {
848 g_free (context->codec_priv);
849 context->codec_priv = NULL;
850 context->codec_priv_size = 0;
853 streamheader = gst_structure_get_value (structure, "streamheader");
854 if (!theora_streamheader_to_codecdata (streamheader, context)) {
855 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
856 ("theora stream headers missing or malformed"));
859 } else if (!strcmp (mimetype, "video/x-dirac")) {
860 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_DIRAC);
861 } else if (!strcmp (mimetype, "video/mpeg")) {
864 gst_structure_get_int (structure, "mpegversion", &mpegversion);
865 switch (mpegversion) {
867 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG1);
870 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG2);
873 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
879 /* global headers may be in codec data */
880 if (codec_buf != NULL) {
881 context->codec_priv_size = GST_BUFFER_SIZE (codec_buf);
882 context->codec_priv = g_malloc0 (context->codec_priv_size);
883 memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
884 context->codec_priv_size);
886 } else if (!strcmp (mimetype, "video/x-msmpeg")) {
888 /* can only make it here if preceding case verified it was version 3 */
889 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3);
890 } else if (!strcmp (mimetype, "video/x-pn-realvideo")) {
892 const GValue *mdpr_data;
894 gst_structure_get_int (structure, "rmversion", &rmversion);
897 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1);
900 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2);
903 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3);
906 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4);
912 mdpr_data = gst_structure_get_value (structure, "mdpr_data");
913 if (mdpr_data != NULL) {
914 guint8 *priv_data = NULL;
915 guint priv_data_size = 0;
917 GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data);
919 priv_data_size = GST_BUFFER_SIZE (codec_data_buf);
920 priv_data = g_malloc0 (priv_data_size);
922 memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size);
924 context->codec_priv = priv_data;
925 context->codec_priv_size = priv_data_size;
934 GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT,
935 GST_PAD_NAME (pad), caps);
940 /* N > 0 to expect a particular number of headers, negative if the
941 number of headers is variable */
943 xiphN_streamheader_to_codecdata (const GValue * streamheader,
944 GstMatroskaTrackContext * context, GstBuffer ** p_buf0, int N)
946 GstBuffer **buf = NULL;
949 guint bufi, i, offset, priv_data_size;
951 if (streamheader == NULL)
952 goto no_stream_headers;
954 if (G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY)
957 bufarr = g_value_peek_pointer (streamheader);
958 if (bufarr->len <= 0 || bufarr->len > 255) /* at least one header, and count stored in a byte */
960 if (N > 0 && bufarr->len != N)
963 context->xiph_headers_to_skip = bufarr->len;
965 buf = (GstBuffer **) g_malloc0 (sizeof (GstBuffer *) * bufarr->len);
966 for (i = 0; i < bufarr->len; i++) {
967 GValue *bufval = &g_array_index (bufarr, GValue, i);
969 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
971 goto wrong_content_type;
974 buf[i] = g_value_peek_pointer (bufval);
978 if (bufarr->len > 0) {
979 for (i = 0; i < bufarr->len - 1; i++) {
980 priv_data_size += GST_BUFFER_SIZE (buf[i]) / 0xff + 1;
984 for (i = 0; i < bufarr->len; ++i) {
985 priv_data_size += GST_BUFFER_SIZE (buf[i]);
988 priv_data = g_malloc0 (priv_data_size);
990 priv_data[0] = bufarr->len - 1;
993 if (bufarr->len > 0) {
994 for (bufi = 0; bufi < bufarr->len - 1; bufi++) {
995 for (i = 0; i < GST_BUFFER_SIZE (buf[bufi]) / 0xff; ++i) {
996 priv_data[offset++] = 0xff;
998 priv_data[offset++] = GST_BUFFER_SIZE (buf[bufi]) % 0xff;
1002 for (i = 0; i < bufarr->len; ++i) {
1003 memcpy (priv_data + offset, GST_BUFFER_DATA (buf[i]),
1004 GST_BUFFER_SIZE (buf[i]));
1005 offset += GST_BUFFER_SIZE (buf[i]);
1008 context->codec_priv = priv_data;
1009 context->codec_priv_size = priv_data_size;
1012 *p_buf0 = gst_buffer_ref (buf[0]);
1021 GST_WARNING ("required streamheaders missing in sink caps!");
1026 GST_WARNING ("streamheaders are not a GST_TYPE_ARRAY, but a %s",
1027 G_VALUE_TYPE_NAME (streamheader));
1032 GST_WARNING ("got %u streamheaders, not %d as expected", bufarr->len, N);
1037 GST_WARNING ("streamheaders array does not contain GstBuffers");
1043 vorbis_streamheader_to_codecdata (const GValue * streamheader,
1044 GstMatroskaTrackContext * context)
1046 GstBuffer *buf0 = NULL;
1048 if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3))
1051 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 4) {
1052 GST_WARNING ("First vorbis header too small, ignoring");
1054 if (memcmp (GST_BUFFER_DATA (buf0) + 1, "vorbis", 6) == 0) {
1055 GstMatroskaTrackAudioContext *audiocontext;
1058 hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 4;
1059 audiocontext = (GstMatroskaTrackAudioContext *) context;
1060 audiocontext->channels = GST_READ_UINT8 (hdr);
1061 audiocontext->samplerate = GST_READ_UINT32_LE (hdr + 1);
1066 gst_buffer_unref (buf0);
1072 theora_streamheader_to_codecdata (const GValue * streamheader,
1073 GstMatroskaTrackContext * context)
1075 GstBuffer *buf0 = NULL;
1077 if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3))
1080 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 26) {
1081 GST_WARNING ("First theora header too small, ignoring");
1082 } else if (memcmp (GST_BUFFER_DATA (buf0), "\200theora\003\002", 9) != 0) {
1083 GST_WARNING ("First header not a theora identification header, ignoring");
1085 GstMatroskaTrackVideoContext *videocontext;
1086 guint fps_num, fps_denom, par_num, par_denom;
1089 hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 3 + 2 + 2;
1091 videocontext = (GstMatroskaTrackVideoContext *) context;
1092 videocontext->pixel_width = GST_READ_UINT32_BE (hdr) >> 8;
1093 videocontext->pixel_height = GST_READ_UINT32_BE (hdr + 3) >> 8;
1094 hdr += 3 + 3 + 1 + 1;
1095 fps_num = GST_READ_UINT32_BE (hdr);
1096 fps_denom = GST_READ_UINT32_BE (hdr + 4);
1097 context->default_duration = gst_util_uint64_scale_int (GST_SECOND,
1098 fps_denom, fps_num);
1100 par_num = GST_READ_UINT32_BE (hdr) >> 8;
1101 par_denom = GST_READ_UINT32_BE (hdr + 3) >> 8;
1102 if (par_num > 0 && par_num > 0) {
1103 if (par_num > par_denom) {
1104 videocontext->display_width =
1105 videocontext->pixel_width * par_num / par_denom;
1106 videocontext->display_height = videocontext->pixel_height;
1107 } else if (par_num < par_denom) {
1108 videocontext->display_width = videocontext->pixel_width;
1109 videocontext->display_height =
1110 videocontext->pixel_height * par_denom / par_num;
1112 videocontext->display_width = 0;
1113 videocontext->display_height = 0;
1116 videocontext->display_width = 0;
1117 videocontext->display_height = 0;
1123 gst_buffer_unref (buf0);
1129 kate_streamheader_to_codecdata (const GValue * streamheader,
1130 GstMatroskaTrackContext * context)
1132 GstBuffer *buf0 = NULL;
1134 if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, -1))
1137 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 64) { /* Kate ID header is 64 bytes */
1138 GST_WARNING ("First kate header too small, ignoring");
1139 } else if (memcmp (GST_BUFFER_DATA (buf0), "\200kate\0\0\0", 8) != 0) {
1140 GST_WARNING ("First header not a kate identification header, ignoring");
1144 gst_buffer_unref (buf0);
1150 flac_streamheader_to_codecdata (const GValue * streamheader,
1151 GstMatroskaTrackContext * context)
1158 if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) {
1159 GST_WARNING ("No or invalid streamheader field in the caps");
1163 bufarr = g_value_peek_pointer (streamheader);
1164 if (bufarr->len < 2) {
1165 GST_WARNING ("Too few headers in streamheader field");
1169 context->xiph_headers_to_skip = bufarr->len + 1;
1171 bufval = &g_array_index (bufarr, GValue, 0);
1172 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1173 GST_WARNING ("streamheaders array does not contain GstBuffers");
1177 buffer = g_value_peek_pointer (bufval);
1179 /* Need at least OggFLAC mapping header, fLaC marker and STREAMINFO block */
1180 if (GST_BUFFER_SIZE (buffer) < 9 + 4 + 4 + 34
1181 || memcmp (GST_BUFFER_DATA (buffer) + 1, "FLAC", 4) != 0
1182 || memcmp (GST_BUFFER_DATA (buffer) + 9, "fLaC", 4) != 0) {
1183 GST_WARNING ("Invalid streamheader for FLAC");
1187 context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer) - 9);
1188 context->codec_priv_size = GST_BUFFER_SIZE (buffer) - 9;
1189 memcpy (context->codec_priv, GST_BUFFER_DATA (buffer) + 9,
1190 GST_BUFFER_SIZE (buffer) - 9);
1192 for (i = 1; i < bufarr->len; i++) {
1193 bufval = &g_array_index (bufarr, GValue, i);
1195 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1196 g_free (context->codec_priv);
1197 context->codec_priv = NULL;
1198 context->codec_priv_size = 0;
1199 GST_WARNING ("streamheaders array does not contain GstBuffers");
1203 buffer = g_value_peek_pointer (bufval);
1205 context->codec_priv =
1206 g_realloc (context->codec_priv,
1207 context->codec_priv_size + GST_BUFFER_SIZE (buffer));
1208 memcpy ((guint8 *) context->codec_priv + context->codec_priv_size,
1209 GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
1210 context->codec_priv_size =
1211 context->codec_priv_size + GST_BUFFER_SIZE (buffer);
1218 speex_streamheader_to_codecdata (const GValue * streamheader,
1219 GstMatroskaTrackContext * context)
1225 if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) {
1226 GST_WARNING ("No or invalid streamheader field in the caps");
1230 bufarr = g_value_peek_pointer (streamheader);
1231 if (bufarr->len != 2) {
1232 GST_WARNING ("Too few headers in streamheader field");
1236 context->xiph_headers_to_skip = bufarr->len + 1;
1238 bufval = &g_array_index (bufarr, GValue, 0);
1239 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1240 GST_WARNING ("streamheaders array does not contain GstBuffers");
1244 buffer = g_value_peek_pointer (bufval);
1246 if (GST_BUFFER_SIZE (buffer) < 80
1247 || memcmp (GST_BUFFER_DATA (buffer), "Speex ", 8) != 0) {
1248 GST_WARNING ("Invalid streamheader for Speex");
1252 context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer));
1253 context->codec_priv_size = GST_BUFFER_SIZE (buffer);
1254 memcpy (context->codec_priv, GST_BUFFER_DATA (buffer),
1255 GST_BUFFER_SIZE (buffer));
1257 bufval = &g_array_index (bufarr, GValue, 1);
1259 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1260 g_free (context->codec_priv);
1261 context->codec_priv = NULL;
1262 context->codec_priv_size = 0;
1263 GST_WARNING ("streamheaders array does not contain GstBuffers");
1267 buffer = g_value_peek_pointer (bufval);
1269 context->codec_priv =
1270 g_realloc (context->codec_priv,
1271 context->codec_priv_size + GST_BUFFER_SIZE (buffer));
1272 memcpy ((guint8 *) context->codec_priv + context->codec_priv_size,
1273 GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
1274 context->codec_priv_size =
1275 context->codec_priv_size + GST_BUFFER_SIZE (buffer);
1281 aac_codec_data_to_codec_id (const GstBuffer * buf)
1286 /* default to MAIN */
1289 if (GST_BUFFER_SIZE (buf) >= 2) {
1290 profile = GST_READ_UINT8 (GST_BUFFER_DATA (buf));
1308 GST_WARNING ("unknown AAC profile, defaulting to MAIN");
1317 * gst_matroska_mux_audio_pad_setcaps:
1318 * @pad: Pad which got the caps.
1321 * Setcaps function for audio sink pad.
1323 * Returns: #TRUE on success.
1326 gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
1328 GstMatroskaTrackContext *context = NULL;
1329 GstMatroskaTrackAudioContext *audiocontext;
1330 GstMatroskaMux *mux;
1331 GstMatroskaPad *collect_pad;
1332 const gchar *mimetype;
1333 gint samplerate = 0, channels = 0;
1334 GstStructure *structure;
1335 const GValue *codec_data = NULL;
1336 const GstBuffer *buf = NULL;
1337 const gchar *stream_format = NULL;
1339 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1342 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
1343 g_assert (collect_pad);
1344 context = collect_pad->track;
1346 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_AUDIO);
1347 audiocontext = (GstMatroskaTrackAudioContext *) context;
1349 structure = gst_caps_get_structure (caps, 0);
1350 mimetype = gst_structure_get_name (structure);
1353 gst_structure_get_int (structure, "rate", &samplerate);
1354 gst_structure_get_int (structure, "channels", &channels);
1356 audiocontext->samplerate = samplerate;
1357 audiocontext->channels = channels;
1358 audiocontext->bitdepth = 0;
1359 context->default_duration = 0;
1361 codec_data = gst_structure_get_value (structure, "codec_data");
1363 buf = gst_value_get_buffer (codec_data);
1365 /* TODO: - check if we handle all codecs by the spec, i.e. codec private
1366 * data and other settings
1370 if (!strcmp (mimetype, "audio/mpeg")) {
1371 gint mpegversion = 0;
1373 gst_structure_get_int (structure, "mpegversion", &mpegversion);
1374 switch (mpegversion) {
1380 gst_structure_get_int (structure, "layer", &layer);
1382 if (!gst_structure_get_int (structure, "mpegaudioversion", &version)) {
1383 GST_WARNING_OBJECT (mux,
1384 "Unable to determine MPEG audio version, assuming 1");
1390 else if (layer == 2)
1392 else if (version == 2)
1397 context->default_duration =
1398 gst_util_uint64_scale (GST_SECOND, spf, audiocontext->samplerate);
1402 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1);
1405 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2);
1408 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3);
1417 stream_format = gst_structure_get_string (structure, "stream-format");
1418 /* check this is raw aac */
1419 if (stream_format) {
1420 if (strcmp (stream_format, "raw") != 0) {
1421 GST_WARNING_OBJECT (mux, "AAC stream-format must be 'raw', not %s",
1425 GST_WARNING_OBJECT (mux, "AAC stream-format not specified, "
1430 if (mpegversion == 2)
1432 g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG2 "%s",
1433 aac_codec_data_to_codec_id (buf));
1434 else if (mpegversion == 4)
1436 g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4 "%s",
1437 aac_codec_data_to_codec_id (buf));
1439 g_assert_not_reached ();
1441 GST_DEBUG_OBJECT (mux, "no AAC codec_data; not packetized");
1448 } else if (!strcmp (mimetype, "audio/x-raw-int")) {
1450 gint endianness = G_LITTLE_ENDIAN;
1451 gboolean signedness = TRUE;
1453 if (!gst_structure_get_int (structure, "width", &width) ||
1454 !gst_structure_get_int (structure, "depth", &depth) ||
1455 !gst_structure_get_boolean (structure, "signed", &signedness)) {
1456 GST_DEBUG_OBJECT (mux, "broken caps, width/depth/signed field missing");
1461 !gst_structure_get_int (structure, "endianness", &endianness)) {
1462 GST_DEBUG_OBJECT (mux, "broken caps, no endianness specified");
1466 if (width != depth) {
1467 GST_DEBUG_OBJECT (mux, "width must be same as depth!");
1471 /* FIXME: where is this spec'ed out? (tpm) */
1472 if ((width == 8 && signedness) || (width >= 16 && !signedness)) {
1473 GST_DEBUG_OBJECT (mux, "8-bit PCM must be unsigned, 16-bit PCM signed");
1477 audiocontext->bitdepth = depth;
1478 if (endianness == G_BIG_ENDIAN)
1479 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE);
1481 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE);
1483 } else if (!strcmp (mimetype, "audio/x-raw-float")) {
1486 if (!gst_structure_get_int (structure, "width", &width)) {
1487 GST_DEBUG_OBJECT (mux, "broken caps, width field missing");
1491 audiocontext->bitdepth = width;
1492 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT);
1494 } else if (!strcmp (mimetype, "audio/x-vorbis")) {
1495 const GValue *streamheader;
1497 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_VORBIS);
1499 if (context->codec_priv != NULL) {
1500 g_free (context->codec_priv);
1501 context->codec_priv = NULL;
1502 context->codec_priv_size = 0;
1505 streamheader = gst_structure_get_value (structure, "streamheader");
1506 if (!vorbis_streamheader_to_codecdata (streamheader, context)) {
1507 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1508 ("vorbis stream headers missing or malformed"));
1511 } else if (!strcmp (mimetype, "audio/x-flac")) {
1512 const GValue *streamheader;
1514 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_FLAC);
1515 if (context->codec_priv != NULL) {
1516 g_free (context->codec_priv);
1517 context->codec_priv = NULL;
1518 context->codec_priv_size = 0;
1521 streamheader = gst_structure_get_value (structure, "streamheader");
1522 if (!flac_streamheader_to_codecdata (streamheader, context)) {
1523 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1524 ("flac stream headers missing or malformed"));
1527 } else if (!strcmp (mimetype, "audio/x-speex")) {
1528 const GValue *streamheader;
1530 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_SPEEX);
1531 if (context->codec_priv != NULL) {
1532 g_free (context->codec_priv);
1533 context->codec_priv = NULL;
1534 context->codec_priv_size = 0;
1537 streamheader = gst_structure_get_value (structure, "streamheader");
1538 if (!speex_streamheader_to_codecdata (streamheader, context)) {
1539 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1540 ("speex stream headers missing or malformed"));
1543 } else if (!strcmp (mimetype, "audio/x-ac3")) {
1544 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3);
1545 } else if (!strcmp (mimetype, "audio/x-tta")) {
1548 /* TTA frame duration */
1549 context->default_duration = 1.04489795918367346939 * GST_SECOND;
1551 gst_structure_get_int (structure, "width", &width);
1552 audiocontext->bitdepth = width;
1553 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_TTA);
1555 } else if (!strcmp (mimetype, "audio/x-pn-realaudio")) {
1557 const GValue *mdpr_data;
1559 gst_structure_get_int (structure, "raversion", &raversion);
1560 switch (raversion) {
1562 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4);
1565 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_28_8);
1568 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK);
1574 mdpr_data = gst_structure_get_value (structure, "mdpr_data");
1575 if (mdpr_data != NULL) {
1576 guint8 *priv_data = NULL;
1577 guint priv_data_size = 0;
1579 GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data);
1581 priv_data_size = GST_BUFFER_SIZE (codec_data_buf);
1582 priv_data = g_malloc0 (priv_data_size);
1584 memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size);
1586 context->codec_priv = priv_data;
1587 context->codec_priv_size = priv_data_size;
1590 } else if (!strcmp (mimetype, "audio/x-wma")) {
1592 guint codec_priv_size;
1599 if (!gst_structure_get_int (structure, "wmaversion", &wmaversion)
1600 || !gst_structure_get_int (structure, "block_align", &block_align)
1601 || !gst_structure_get_int (structure, "bitrate", &bitrate)
1602 || samplerate == 0 || channels == 0) {
1603 GST_WARNING_OBJECT (mux, "Missing wmaversion/block_align/bitrate/"
1604 "channels/rate on WMA caps");
1608 switch (wmaversion) {
1610 format = GST_RIFF_WAVE_FORMAT_WMAV1;
1613 format = GST_RIFF_WAVE_FORMAT_WMAV2;
1616 format = GST_RIFF_WAVE_FORMAT_WMAV3;
1619 GST_WARNING_OBJECT (mux, "Unexpected WMA version: %d", wmaversion);
1623 if (gst_structure_get_int (structure, "depth", &depth))
1624 audiocontext->bitdepth = depth;
1626 codec_priv_size = WAVEFORMATEX_SIZE;
1628 codec_priv_size += GST_BUFFER_SIZE (buf);
1630 /* serialize waveformatex structure */
1631 codec_priv = g_malloc0 (codec_priv_size);
1632 GST_WRITE_UINT16_LE (codec_priv, format);
1633 GST_WRITE_UINT16_LE (codec_priv + 2, channels);
1634 GST_WRITE_UINT32_LE (codec_priv + 4, samplerate);
1635 GST_WRITE_UINT32_LE (codec_priv + 8, bitrate / 8);
1636 GST_WRITE_UINT16_LE (codec_priv + 12, block_align);
1637 GST_WRITE_UINT16_LE (codec_priv + 14, 0);
1639 GST_WRITE_UINT16_LE (codec_priv + 16, GST_BUFFER_SIZE (buf));
1641 GST_WRITE_UINT16_LE (codec_priv + 16, 0);
1643 /* process codec private/initialization data, if any */
1645 memcpy ((guint8 *) codec_priv + WAVEFORMATEX_SIZE,
1646 GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
1649 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_ACM);
1650 context->codec_priv = (gpointer) codec_priv;
1651 context->codec_priv_size = codec_priv_size;
1659 GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT,
1660 GST_PAD_NAME (pad), caps);
1667 * gst_matroska_mux_subtitle_pad_setcaps:
1668 * @pad: Pad which got the caps.
1671 * Setcaps function for subtitle sink pad.
1673 * Returns: #TRUE on success.
1676 gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps)
1679 * Consider this as boilerplate code for now. There is
1680 * no single subtitle creation element in GStreamer,
1681 * neither do I know how subtitling works at all. */
1683 /* There is now (at least) one such alement (kateenc), and I'm going
1684 to handle it here and claim it works when it can be piped back
1685 through GStreamer and VLC */
1687 GstMatroskaTrackContext *context = NULL;
1688 GstMatroskaTrackSubtitleContext *scontext;
1689 GstMatroskaMux *mux;
1690 GstMatroskaPad *collect_pad;
1691 const gchar *mimetype;
1692 GstStructure *structure;
1694 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1697 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
1698 g_assert (collect_pad);
1699 context = collect_pad->track;
1701 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_SUBTITLE);
1702 scontext = (GstMatroskaTrackSubtitleContext *) context;
1704 structure = gst_caps_get_structure (caps, 0);
1705 mimetype = gst_structure_get_name (structure);
1708 scontext->check_utf8 = 1;
1709 scontext->invalid_utf8 = 0;
1710 context->default_duration = 0;
1712 /* TODO: - other format than Kate */
1714 if (!strcmp (mimetype, "subtitle/x-kate")) {
1715 const GValue *streamheader;
1717 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_SUBTITLE_KATE);
1719 if (context->codec_priv != NULL) {
1720 g_free (context->codec_priv);
1721 context->codec_priv = NULL;
1722 context->codec_priv_size = 0;
1725 streamheader = gst_structure_get_value (structure, "streamheader");
1726 if (!kate_streamheader_to_codecdata (streamheader, context)) {
1727 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1728 ("kate stream headers missing or malformed"));
1739 * gst_matroska_mux_request_new_pad:
1740 * @element: #GstMatroskaMux.
1741 * @templ: #GstPadTemplate.
1742 * @pad_name: New pad name.
1744 * Request pad function for sink templates.
1746 * Returns: New #GstPad.
1749 gst_matroska_mux_request_new_pad (GstElement * element,
1750 GstPadTemplate * templ, const gchar * pad_name)
1752 GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
1753 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
1754 GstMatroskaPad *collect_pad;
1755 GstPad *newpad = NULL;
1757 GstPadSetCapsFunction setcapsfunc = NULL;
1758 GstMatroskaTrackContext *context = NULL;
1760 if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
1761 name = g_strdup_printf ("audio_%d", mux->num_a_streams++);
1762 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_audio_pad_setcaps);
1763 context = (GstMatroskaTrackContext *)
1764 g_new0 (GstMatroskaTrackAudioContext, 1);
1765 context->type = GST_MATROSKA_TRACK_TYPE_AUDIO;
1766 context->name = g_strdup ("Audio");
1767 } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
1768 name = g_strdup_printf ("video_%d", mux->num_v_streams++);
1769 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_video_pad_setcaps);
1770 context = (GstMatroskaTrackContext *)
1771 g_new0 (GstMatroskaTrackVideoContext, 1);
1772 context->type = GST_MATROSKA_TRACK_TYPE_VIDEO;
1773 context->name = g_strdup ("Video");
1774 } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%d")) {
1775 name = g_strdup_printf ("subtitle_%d", mux->num_t_streams++);
1776 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_subtitle_pad_setcaps);
1777 context = (GstMatroskaTrackContext *)
1778 g_new0 (GstMatroskaTrackSubtitleContext, 1);
1779 context->type = GST_MATROSKA_TRACK_TYPE_SUBTITLE;
1780 context->name = g_strdup ("Subtitle");
1782 GST_WARNING_OBJECT (mux, "This is not our template!");
1786 newpad = gst_pad_new_from_template (templ, name);
1788 collect_pad = (GstMatroskaPad *)
1789 gst_collect_pads_add_pad_full (mux->collect, newpad,
1790 sizeof (GstMatroskaPad),
1791 (GstCollectDataDestroyNotify) gst_matroska_pad_free);
1793 collect_pad->track = context;
1794 gst_matroska_pad_reset (collect_pad, FALSE);
1796 /* FIXME: hacked way to override/extend the event function of
1797 * GstCollectPads; because it sets its own event function giving the
1798 * element no access to events.
1799 * TODO GstCollectPads should really give its 'users' a clean chance to
1800 * properly handle events that are not meant for collectpads itself.
1801 * Perhaps a callback or so, though rejected (?) in #340060.
1802 * This would allow (clean) transcoding of info from demuxer/streams
1803 * to another muxer */
1804 mux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
1805 gst_pad_set_event_function (newpad,
1806 GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_sink_event));
1808 gst_pad_set_setcaps_function (newpad, setcapsfunc);
1809 gst_pad_set_active (newpad, TRUE);
1810 gst_element_add_pad (element, newpad);
1817 * gst_matroska_mux_release_pad:
1818 * @element: #GstMatroskaMux.
1819 * @pad: Pad to release.
1821 * Release a previously requested pad.
1824 gst_matroska_mux_release_pad (GstElement * element, GstPad * pad)
1826 GstMatroskaMux *mux;
1829 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1831 for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
1832 GstCollectData *cdata = (GstCollectData *) walk->data;
1833 GstMatroskaPad *collect_pad = (GstMatroskaPad *) cdata;
1835 if (cdata->pad == pad) {
1836 GstClockTime min_dur; /* observed minimum duration */
1838 if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) &&
1839 GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) {
1840 min_dur = GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts);
1841 if (collect_pad->duration < min_dur)
1842 collect_pad->duration = min_dur;
1845 if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
1846 mux->duration < collect_pad->duration)
1847 mux->duration = collect_pad->duration;
1853 gst_collect_pads_remove_pad (mux->collect, pad);
1854 if (gst_element_remove_pad (element, pad))
1860 * gst_matroska_mux_track_header:
1861 * @mux: #GstMatroskaMux
1862 * @context: Tack context.
1864 * Write a track header.
1867 gst_matroska_mux_track_header (GstMatroskaMux * mux,
1868 GstMatroskaTrackContext * context)
1870 GstEbmlWrite *ebml = mux->ebml_write;
1873 /* TODO: check if everything necessary is written and check default values */
1875 /* track type goes before the type-specific stuff */
1876 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKNUMBER, context->num);
1877 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKTYPE, context->type);
1879 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKUID,
1880 gst_matroska_mux_create_uid ());
1881 if (context->default_duration) {
1882 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKDEFAULTDURATION,
1883 context->default_duration);
1885 if (context->language) {
1886 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKLANGUAGE,
1890 /* type-specific stuff */
1891 switch (context->type) {
1892 case GST_MATROSKA_TRACK_TYPE_VIDEO:{
1893 GstMatroskaTrackVideoContext *videocontext =
1894 (GstMatroskaTrackVideoContext *) context;
1896 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKVIDEO);
1897 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELWIDTH,
1898 videocontext->pixel_width);
1899 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELHEIGHT,
1900 videocontext->pixel_height);
1901 if (videocontext->display_width && videocontext->display_height) {
1902 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYWIDTH,
1903 videocontext->display_width);
1904 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYHEIGHT,
1905 videocontext->display_height);
1907 if (context->flags & GST_MATROSKA_VIDEOTRACK_INTERLACED)
1908 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOFLAGINTERLACED, 1);
1909 if (videocontext->fourcc) {
1910 guint32 fcc_le = GUINT32_TO_LE (videocontext->fourcc);
1912 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_VIDEOCOLOURSPACE,
1913 (gpointer) & fcc_le, 4);
1915 gst_ebml_write_master_finish (ebml, master);
1920 case GST_MATROSKA_TRACK_TYPE_AUDIO:{
1921 GstMatroskaTrackAudioContext *audiocontext =
1922 (GstMatroskaTrackAudioContext *) context;
1924 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKAUDIO);
1925 if (audiocontext->samplerate != 8000)
1926 gst_ebml_write_float (ebml, GST_MATROSKA_ID_AUDIOSAMPLINGFREQ,
1927 audiocontext->samplerate);
1928 if (audiocontext->channels != 1)
1929 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOCHANNELS,
1930 audiocontext->channels);
1931 if (audiocontext->bitdepth) {
1932 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOBITDEPTH,
1933 audiocontext->bitdepth);
1935 gst_ebml_write_master_finish (ebml, master);
1941 /* doesn't need type-specific data */
1945 gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CODECID, context->codec_id);
1946 if (context->codec_priv)
1947 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECPRIVATE,
1948 context->codec_priv, context->codec_priv_size);
1949 /* FIXME: until we have a nice way of getting the codecname
1950 * out of the caps, I'm not going to enable this. Too much
1951 * (useless, double, boring) work... */
1952 /* TODO: Use value from tags if any */
1953 /*gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CODECNAME,
1954 context->codec_name); */
1955 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKNAME, context->name);
1960 * gst_matroska_mux_start:
1961 * @mux: #GstMatroskaMux
1963 * Start a new matroska file (write headers etc...)
1966 gst_matroska_mux_start (GstMatroskaMux * mux)
1968 GstEbmlWrite *ebml = mux->ebml_write;
1969 guint32 seekhead_id[] = { GST_MATROSKA_ID_SEGMENTINFO,
1970 GST_MATROSKA_ID_TRACKS,
1971 GST_MATROSKA_ID_CUES,
1972 GST_MATROSKA_ID_TAGS,
1975 guint64 master, child;
1979 GstClockTime duration = 0;
1980 guint32 segment_uid[4];
1981 GTimeVal time = { 0, 0 };
1983 /* we start with a EBML header */
1984 gst_ebml_write_header (ebml, "matroska", mux->matroska_version);
1986 /* start a segment */
1988 gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENT);
1989 mux->segment_master = ebml->pos;
1991 /* the rest of the header is cached */
1992 gst_ebml_write_set_cache (ebml, 0x1000);
1994 /* seekhead (table of contents) - we set the positions later */
1995 mux->seekhead_pos = ebml->pos;
1996 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD);
1997 for (i = 0; seekhead_id[i] != 0; i++) {
1998 child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKENTRY);
1999 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, seekhead_id[i]);
2000 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION, -1);
2001 gst_ebml_write_master_finish (ebml, child);
2003 gst_ebml_write_master_finish (ebml, master);
2006 mux->info_pos = ebml->pos;
2007 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENTINFO);
2008 for (i = 0; i < 4; i++) {
2009 segment_uid[i] = g_random_int ();
2011 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_SEGMENTUID,
2012 (guint8 *) segment_uid, 16);
2013 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TIMECODESCALE, mux->time_scale);
2014 mux->duration_pos = ebml->pos;
2016 for (collected = mux->collect->data; collected;
2017 collected = g_slist_next (collected)) {
2018 GstMatroskaPad *collect_pad;
2019 GstFormat format = GST_FORMAT_TIME;
2021 gint64 trackduration;
2023 collect_pad = (GstMatroskaPad *) collected->data;
2024 thepad = collect_pad->collect.pad;
2026 /* Query the total length of the track. */
2027 GST_DEBUG_OBJECT (thepad, "querying peer duration");
2028 if (gst_pad_query_peer_duration (thepad, &format, &trackduration)) {
2029 GST_DEBUG_OBJECT (thepad, "duration: %" GST_TIME_FORMAT,
2030 GST_TIME_ARGS (trackduration));
2031 if (trackduration != GST_CLOCK_TIME_NONE && trackduration > duration) {
2032 duration = (GstClockTime) trackduration;
2036 gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
2037 gst_guint64_to_gdouble (duration) /
2038 gst_guint64_to_gdouble (mux->time_scale));
2040 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_MUXINGAPP,
2041 "GStreamer plugin version " PACKAGE_VERSION);
2042 if (mux->writing_app && mux->writing_app[0]) {
2043 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_WRITINGAPP, mux->writing_app);
2045 g_get_current_time (&time);
2046 gst_ebml_write_date (ebml, GST_MATROSKA_ID_DATEUTC, time.tv_sec);
2047 gst_ebml_write_master_finish (ebml, master);
2050 mux->tracks_pos = ebml->pos;
2051 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKS);
2053 for (collected = mux->collect->data; collected;
2054 collected = g_slist_next (collected)) {
2055 GstMatroskaPad *collect_pad;
2058 collect_pad = (GstMatroskaPad *) collected->data;
2059 thepad = collect_pad->collect.pad;
2061 if (gst_pad_is_linked (thepad) && gst_pad_is_active (thepad) &&
2062 collect_pad->track->codec_id != 0) {
2063 collect_pad->track->num = tracknum++;
2064 child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKENTRY);
2065 gst_matroska_mux_track_header (mux, collect_pad->track);
2066 gst_ebml_write_master_finish (ebml, child);
2069 gst_ebml_write_master_finish (ebml, master);
2071 /* lastly, flush the cache */
2072 gst_ebml_write_flush_cache (ebml);
2076 gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag,
2079 /* TODO: more sensible tag mappings */
2082 gchar *matroska_tagname;
2083 gchar *gstreamer_tagname;
2087 GST_MATROSKA_TAG_ID_TITLE, GST_TAG_TITLE}, {
2088 GST_MATROSKA_TAG_ID_AUTHOR, GST_TAG_ARTIST}, {
2089 GST_MATROSKA_TAG_ID_ALBUM, GST_TAG_ALBUM}, {
2090 GST_MATROSKA_TAG_ID_COMMENTS, GST_TAG_COMMENT}, {
2091 GST_MATROSKA_TAG_ID_BITSPS, GST_TAG_BITRATE}, {
2092 GST_MATROSKA_TAG_ID_BPS, GST_TAG_BITRATE}, {
2093 GST_MATROSKA_TAG_ID_ENCODER, GST_TAG_ENCODER}, {
2094 GST_MATROSKA_TAG_ID_DATE, GST_TAG_DATE}, {
2095 GST_MATROSKA_TAG_ID_ISRC, GST_TAG_ISRC}, {
2096 GST_MATROSKA_TAG_ID_COPYRIGHT, GST_TAG_COPYRIGHT}, {
2097 GST_MATROSKA_TAG_ID_BPM, GST_TAG_BEATS_PER_MINUTE}, {
2098 GST_MATROSKA_TAG_ID_TERMS_OF_USE, GST_TAG_LICENSE}, {
2099 GST_MATROSKA_TAG_ID_COMPOSER, GST_TAG_COMPOSER}, {
2100 GST_MATROSKA_TAG_ID_LEAD_PERFORMER, GST_TAG_PERFORMER}, {
2101 GST_MATROSKA_TAG_ID_GENRE, GST_TAG_GENRE}
2103 GstEbmlWrite *ebml = (GstEbmlWrite *) data;
2105 guint64 simpletag_master;
2107 for (i = 0; i < G_N_ELEMENTS (tag_conv); i++) {
2108 const gchar *tagname_gst = tag_conv[i].gstreamer_tagname;
2109 const gchar *tagname_mkv = tag_conv[i].matroska_tagname;
2111 if (strcmp (tagname_gst, tag) == 0) {
2112 GValue src = { 0, };
2115 if (!gst_tag_list_copy_value (&src, list, tag))
2117 if ((dest = gst_value_serialize (&src))) {
2119 simpletag_master = gst_ebml_write_master_start (ebml,
2120 GST_MATROSKA_ID_SIMPLETAG);
2121 gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_TAGNAME, tagname_mkv);
2122 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TAGSTRING, dest);
2123 gst_ebml_write_master_finish (ebml, simpletag_master);
2126 GST_WARNING ("Can't transform tag '%s' to string", tagname_mkv);
2128 g_value_unset (&src);
2136 * gst_matroska_mux_finish:
2137 * @mux: #GstMatroskaMux
2139 * Finish a new matroska file (write index etc...)
2142 gst_matroska_mux_finish (GstMatroskaMux * mux)
2144 GstEbmlWrite *ebml = mux->ebml_write;
2146 guint64 duration = 0;
2148 const GstTagList *tags;
2150 /* finish last cluster */
2152 gst_ebml_write_master_finish (ebml, mux->cluster);
2156 if (mux->index != NULL) {
2158 guint64 master, pointentry_master, trackpos_master;
2160 mux->cues_pos = ebml->pos;
2161 gst_ebml_write_set_cache (ebml, 12 + 41 * mux->num_indexes);
2162 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CUES);
2164 for (n = 0; n < mux->num_indexes; n++) {
2165 GstMatroskaIndex *idx = &mux->index[n];
2167 pointentry_master = gst_ebml_write_master_start (ebml,
2168 GST_MATROSKA_ID_POINTENTRY);
2169 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETIME,
2170 idx->time / mux->time_scale);
2171 trackpos_master = gst_ebml_write_master_start (ebml,
2172 GST_MATROSKA_ID_CUETRACKPOSITIONS);
2173 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETRACK, idx->track);
2174 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUECLUSTERPOSITION,
2175 idx->pos - mux->segment_master);
2176 gst_ebml_write_master_finish (ebml, trackpos_master);
2177 gst_ebml_write_master_finish (ebml, pointentry_master);
2180 gst_ebml_write_master_finish (ebml, master);
2181 gst_ebml_write_flush_cache (ebml);
2185 tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (mux));
2187 if (tags != NULL && !gst_tag_list_is_empty (tags)) {
2188 guint64 master_tags, master_tag;
2190 GST_DEBUG ("Writing tags");
2192 /* TODO: maybe limit via the TARGETS id by looking at the source pad */
2193 mux->tags_pos = ebml->pos;
2194 master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS);
2195 master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG);
2196 gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml);
2197 gst_ebml_write_master_finish (ebml, master_tag);
2198 gst_ebml_write_master_finish (ebml, master_tags);
2201 /* update seekhead. We know that:
2202 * - a seekhead contains 4 entries.
2203 * - order of entries is as above.
2204 * - a seekhead has a 4-byte header + 8-byte length
2205 * - each entry is 2-byte master, 2-byte ID pointer,
2206 * 2-byte length pointer, all 8/1-byte length, 4-
2207 * byte ID and 8-byte length pointer, where the
2208 * length pointer starts at 20.
2209 * - all entries are local to the segment (so pos - segment_master).
2210 * - so each entry is at 12 + 20 + num * 28. */
2211 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 32,
2212 mux->info_pos - mux->segment_master);
2213 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 60,
2214 mux->tracks_pos - mux->segment_master);
2215 if (mux->index != NULL) {
2216 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88,
2217 mux->cues_pos - mux->segment_master);
2220 guint64 my_pos = ebml->pos;
2222 gst_ebml_write_seek (ebml, mux->seekhead_pos + 68);
2223 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
2224 gst_ebml_write_seek (ebml, my_pos);
2227 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116,
2228 mux->tags_pos - mux->segment_master);
2231 guint64 my_pos = ebml->pos;
2233 gst_ebml_write_seek (ebml, mux->seekhead_pos + 96);
2234 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
2235 gst_ebml_write_seek (ebml, my_pos);
2238 /* update duration */
2239 /* first get the overall duration */
2240 /* a released track may have left a duration in here */
2241 duration = mux->duration;
2242 for (collected = mux->collect->data; collected;
2243 collected = g_slist_next (collected)) {
2244 GstMatroskaPad *collect_pad;
2245 GstClockTime min_duration; /* observed minimum duration */
2247 collect_pad = (GstMatroskaPad *) collected->data;
2249 GST_DEBUG_OBJECT (mux, "Pad %" GST_PTR_FORMAT " start ts %" GST_TIME_FORMAT
2250 " end ts %" GST_TIME_FORMAT, collect_pad,
2251 GST_TIME_ARGS (collect_pad->start_ts),
2252 GST_TIME_ARGS (collect_pad->end_ts));
2254 if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) &&
2255 GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) {
2257 GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts);
2258 if (collect_pad->duration < min_duration)
2259 collect_pad->duration = min_duration;
2260 GST_DEBUG_OBJECT (collect_pad, "final track duration: %" GST_TIME_FORMAT,
2261 GST_TIME_ARGS (collect_pad->duration));
2264 if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
2265 duration < collect_pad->duration)
2266 duration = collect_pad->duration;
2268 if (duration != 0) {
2269 GST_DEBUG_OBJECT (mux, "final total duration: %" GST_TIME_FORMAT,
2270 GST_TIME_ARGS (duration));
2271 pos = mux->ebml_write->pos;
2272 gst_ebml_write_seek (ebml, mux->duration_pos);
2273 gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
2274 gst_guint64_to_gdouble (duration) /
2275 gst_guint64_to_gdouble (mux->time_scale));
2276 gst_ebml_write_seek (ebml, pos);
2279 guint64 my_pos = ebml->pos;
2281 gst_ebml_write_seek (ebml, mux->duration_pos);
2282 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 8);
2283 gst_ebml_write_seek (ebml, my_pos);
2286 /* finish segment - this also writes element length */
2287 gst_ebml_write_master_finish (ebml, mux->segment_pos);
2292 * gst_matroska_mux_best_pad:
2293 * @mux: #GstMatroskaMux
2294 * @popped: True if at least one buffer was popped from #GstCollectPads
2296 * Find a pad with the oldest data
2297 * (data from this pad should be written first).
2299 * Returns: Selected pad.
2301 static GstMatroskaPad *
2302 gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped)
2305 GstMatroskaPad *best = NULL;
2308 for (collected = mux->collect->data; collected;
2309 collected = g_slist_next (collected)) {
2310 GstMatroskaPad *collect_pad;
2312 collect_pad = (GstMatroskaPad *) collected->data;
2313 /* fetch a new buffer if needed */
2314 if (collect_pad->buffer == NULL) {
2315 collect_pad->buffer = gst_collect_pads_pop (mux->collect,
2316 (GstCollectData *) collect_pad);
2318 if (collect_pad->buffer != NULL)
2322 /* if we have a buffer check if it is better then the current best one */
2323 if (collect_pad->buffer != NULL) {
2324 if (best == NULL || !GST_BUFFER_TIMESTAMP_IS_VALID (collect_pad->buffer)
2325 || (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)
2326 && GST_BUFFER_TIMESTAMP (collect_pad->buffer) <
2327 GST_BUFFER_TIMESTAMP (best->buffer))) {
2337 * gst_matroska_mux_buffer_header:
2338 * @track: Track context.
2339 * @relative_timestamp: relative timestamp of the buffer
2340 * @flags: Buffer flags.
2342 * Create a buffer containing buffer header.
2344 * Returns: New buffer.
2347 gst_matroska_mux_create_buffer_header (GstMatroskaTrackContext * track,
2348 gint16 relative_timestamp, int flags)
2352 hdr = gst_buffer_new_and_alloc (4);
2353 /* track num - FIXME: what if num >= 0x80 (unlikely)? */
2354 GST_BUFFER_DATA (hdr)[0] = track->num | 0x80;
2355 /* time relative to clustertime */
2356 GST_WRITE_UINT16_BE (GST_BUFFER_DATA (hdr) + 1, relative_timestamp);
2359 GST_BUFFER_DATA (hdr)[3] = flags;
2364 #define DIRAC_PARSE_CODE_SEQUENCE_HEADER 0x00
2365 #define DIRAC_PARSE_CODE_END_OF_SEQUENCE 0x10
2366 #define DIRAC_PARSE_CODE_IS_PICTURE(x) ((x & 0x08) != 0)
2369 gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux,
2370 GstMatroskaPad * collect_pad, GstBuffer * buf)
2372 GstMatroskaTrackVideoContext *ctx =
2373 (GstMatroskaTrackVideoContext *) collect_pad->track;
2374 const guint8 *data = GST_BUFFER_DATA (buf);
2375 guint size = GST_BUFFER_SIZE (buf);
2377 guint32 next_parse_offset;
2378 GstBuffer *ret = NULL;
2379 gboolean is_muxing_unit = FALSE;
2381 if (GST_BUFFER_SIZE (buf) < 13) {
2382 gst_buffer_unref (buf);
2386 /* Check if this buffer contains a picture or end-of-sequence packet */
2387 while (size >= 13) {
2388 if (GST_READ_UINT32_BE (data) != 0x42424344 /* 'BBCD' */ ) {
2389 gst_buffer_unref (buf);
2393 parse_code = GST_READ_UINT8 (data + 4);
2394 if (parse_code == DIRAC_PARSE_CODE_SEQUENCE_HEADER) {
2395 if (ctx->dirac_unit) {
2396 gst_buffer_unref (ctx->dirac_unit);
2397 ctx->dirac_unit = NULL;
2399 } else if (DIRAC_PARSE_CODE_IS_PICTURE (parse_code) ||
2400 parse_code == DIRAC_PARSE_CODE_END_OF_SEQUENCE) {
2401 is_muxing_unit = TRUE;
2405 next_parse_offset = GST_READ_UINT32_BE (data + 5);
2407 if (G_UNLIKELY (next_parse_offset == 0))
2410 data += next_parse_offset;
2411 size -= next_parse_offset;
2414 if (ctx->dirac_unit)
2415 ctx->dirac_unit = gst_buffer_join (ctx->dirac_unit, gst_buffer_ref (buf));
2417 ctx->dirac_unit = gst_buffer_ref (buf);
2419 if (is_muxing_unit) {
2420 ret = gst_buffer_make_metadata_writable (ctx->dirac_unit);
2421 ctx->dirac_unit = NULL;
2422 gst_buffer_copy_metadata (ret, buf,
2423 GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS |
2424 GST_BUFFER_COPY_CAPS);
2425 gst_buffer_unref (buf);
2427 gst_buffer_unref (buf);
2435 * gst_matroska_mux_write_data:
2436 * @mux: #GstMatroskaMux
2437 * @collect_pad: #GstMatroskaPad with the data
2439 * Write collected data (called from gst_matroska_mux_collected).
2441 * Returns: Result of the gst_pad_push issued to write the data.
2443 static GstFlowReturn
2444 gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
2446 GstEbmlWrite *ebml = mux->ebml_write;
2447 GstBuffer *buf, *hdr;
2449 gboolean write_duration;
2450 gint16 relative_timestamp;
2451 gint64 relative_timestamp64;
2452 guint64 block_duration;
2453 gboolean is_video_keyframe = FALSE;
2456 buf = collect_pad->buffer;
2457 collect_pad->buffer = NULL;
2459 /* vorbis/theora headers are retrieved from caps and put in CodecPrivate */
2460 if (collect_pad->track->xiph_headers_to_skip > 0) {
2461 GST_LOG_OBJECT (collect_pad->collect.pad, "dropping streamheader buffer");
2462 gst_buffer_unref (buf);
2463 --collect_pad->track->xiph_headers_to_skip;
2467 /* for dirac we have to queue up everything up to a picture unit */
2468 if (collect_pad->track->codec_id != NULL &&
2469 strcmp (collect_pad->track->codec_id,
2470 GST_MATROSKA_CODEC_ID_VIDEO_DIRAC) == 0) {
2471 buf = gst_matroska_mux_handle_dirac_packet (mux, collect_pad, buf);
2476 /* hm, invalid timestamp (due to --to be fixed--- element upstream);
2477 * this would wreak havoc with time stored in matroska file */
2478 /* TODO: maybe calculate a timestamp by using the previous timestamp
2479 * and default duration */
2480 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
2481 GST_WARNING_OBJECT (collect_pad->collect.pad,
2482 "Invalid buffer timestamp; dropping buffer");
2483 gst_buffer_unref (buf);
2487 /* set the timestamp for outgoing buffers */
2488 ebml->timestamp = GST_BUFFER_TIMESTAMP (buf);
2490 if (collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_VIDEO &&
2491 !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
2492 GST_LOG_OBJECT (mux, "have video keyframe, ts=%" GST_TIME_FORMAT,
2493 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
2494 is_video_keyframe = TRUE;
2498 /* start a new cluster every two seconds or at keyframe */
2499 if (mux->cluster_time + GST_SECOND * 2 < GST_BUFFER_TIMESTAMP (buf)
2500 || is_video_keyframe) {
2502 gst_ebml_write_master_finish (ebml, mux->cluster);
2503 mux->cluster_pos = ebml->pos;
2505 gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
2506 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
2507 GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
2508 mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
2513 mux->cluster_pos = ebml->pos;
2514 mux->cluster = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
2515 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
2516 GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
2517 mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
2520 /* update duration of this track */
2521 if (GST_BUFFER_DURATION_IS_VALID (buf))
2522 collect_pad->duration += GST_BUFFER_DURATION (buf);
2524 /* We currently write index entries for all video tracks or for the audio
2525 * track in a single-track audio file. This could be improved by keeping the
2526 * index only for the *first* video track. */
2528 /* TODO: index is useful for every track, should contain the number of
2529 * the block in the cluster which contains the timestamp, should also work
2530 * for files with multiple audio tracks.
2532 if (is_video_keyframe ||
2533 ((collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_AUDIO) &&
2534 (mux->num_streams == 1))) {
2537 if (mux->min_index_interval != 0) {
2538 for (last_idx = mux->num_indexes - 1; last_idx >= 0; last_idx--) {
2539 if (mux->index[last_idx].track == collect_pad->track->num)
2544 if (last_idx < 0 || mux->min_index_interval == 0 ||
2545 (GST_CLOCK_DIFF (mux->index[last_idx].time, GST_BUFFER_TIMESTAMP (buf))
2546 >= mux->min_index_interval)) {
2547 GstMatroskaIndex *idx;
2549 if (mux->num_indexes % 32 == 0) {
2550 mux->index = g_renew (GstMatroskaIndex, mux->index,
2551 mux->num_indexes + 32);
2553 idx = &mux->index[mux->num_indexes++];
2555 idx->pos = mux->cluster_pos;
2556 idx->time = GST_BUFFER_TIMESTAMP (buf);
2557 idx->track = collect_pad->track->num;
2561 /* Check if the duration differs from the default duration. */
2562 write_duration = FALSE;
2563 block_duration = GST_BUFFER_DURATION (buf);
2564 if (GST_BUFFER_DURATION_IS_VALID (buf)) {
2565 if (block_duration != collect_pad->track->default_duration) {
2566 write_duration = TRUE;
2570 /* write the block, for matroska v2 use SimpleBlock if possible
2571 * one slice (*breath*).
2572 * FIXME: Need to do correct lacing! */
2573 relative_timestamp64 = GST_BUFFER_TIMESTAMP (buf) - mux->cluster_time;
2574 if (relative_timestamp64 >= 0) {
2575 /* round the timestamp */
2576 relative_timestamp64 += mux->time_scale / 2;
2578 /* round the timestamp */
2579 relative_timestamp64 -= mux->time_scale / 2;
2581 relative_timestamp = relative_timestamp64 / (gint64) mux->time_scale;
2582 if (mux->matroska_version > 1 && !write_duration) {
2584 GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) ? 0 : 0x80;
2587 gst_matroska_mux_create_buffer_header (collect_pad->track,
2588 relative_timestamp, flags);
2589 gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_SIMPLEBLOCK,
2590 GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
2591 gst_ebml_write_buffer (ebml, hdr);
2592 gst_ebml_write_buffer (ebml, buf);
2594 return gst_ebml_last_write_result (ebml);
2596 blockgroup = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_BLOCKGROUP);
2598 gst_matroska_mux_create_buffer_header (collect_pad->track,
2599 relative_timestamp, 0);
2600 gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_BLOCK,
2601 GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
2602 gst_ebml_write_buffer (ebml, hdr);
2603 gst_ebml_write_buffer (ebml, buf);
2604 if (write_duration) {
2605 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_BLOCKDURATION,
2606 block_duration / mux->time_scale);
2608 gst_ebml_write_master_finish (ebml, blockgroup);
2609 return gst_ebml_last_write_result (ebml);
2615 * gst_matroska_mux_collected:
2616 * @pads: #GstCollectPads
2617 * @uuser_data: #GstMatroskaMux
2619 * Collectpads callback.
2621 * Returns: #GstFlowReturn
2623 static GstFlowReturn
2624 gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data)
2626 GstMatroskaMux *mux = GST_MATROSKA_MUX (user_data);
2627 GstMatroskaPad *best;
2631 GST_DEBUG_OBJECT (mux, "Collected pads");
2633 /* start with a header */
2634 if (mux->state == GST_MATROSKA_MUX_STATE_START) {
2635 if (mux->collect->data == NULL) {
2636 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
2637 ("No input streams configured"));
2638 return GST_FLOW_ERROR;
2640 mux->state = GST_MATROSKA_MUX_STATE_HEADER;
2641 gst_matroska_mux_start (mux);
2642 mux->state = GST_MATROSKA_MUX_STATE_DATA;
2646 /* which stream to write from? */
2647 best = gst_matroska_mux_best_pad (mux, &popped);
2649 /* if there is no best pad, we have reached EOS */
2651 GST_DEBUG_OBJECT (mux, "No best pad finishing...");
2652 gst_matroska_mux_finish (mux);
2653 gst_pad_push_event (mux->srcpad, gst_event_new_eos ());
2654 ret = GST_FLOW_UNEXPECTED;
2657 GST_DEBUG_OBJECT (best->collect.pad, "best pad - buffer ts %"
2658 GST_TIME_FORMAT " dur %" GST_TIME_FORMAT,
2659 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (best->buffer)),
2660 GST_TIME_ARGS (GST_BUFFER_DURATION (best->buffer)));
2662 /* make note of first and last encountered timestamps, so we can calculate
2663 * the actual duration later when we send an updated header on eos */
2664 if (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)) {
2665 GstClockTime start_ts = GST_BUFFER_TIMESTAMP (best->buffer);
2666 GstClockTime end_ts = start_ts;
2668 if (GST_BUFFER_DURATION_IS_VALID (best->buffer))
2669 end_ts += GST_BUFFER_DURATION (best->buffer);
2670 else if (best->track->default_duration)
2671 end_ts += best->track->default_duration;
2673 if (!GST_CLOCK_TIME_IS_VALID (best->end_ts) || end_ts > best->end_ts)
2674 best->end_ts = end_ts;
2676 if (G_UNLIKELY (best->start_ts == GST_CLOCK_TIME_NONE ||
2677 start_ts < best->start_ts))
2678 best->start_ts = start_ts;
2681 /* write one buffer */
2682 ret = gst_matroska_mux_write_data (mux, best);
2683 } while (ret == GST_FLOW_OK && !popped);
2690 * gst_matroska_mux_change_state:
2691 * @element: #GstMatroskaMux
2692 * @transition: State change transition.
2694 * Change the muxer state.
2696 * Returns: #GstStateChangeReturn
2698 static GstStateChangeReturn
2699 gst_matroska_mux_change_state (GstElement * element, GstStateChange transition)
2701 GstStateChangeReturn ret;
2702 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
2704 switch (transition) {
2705 case GST_STATE_CHANGE_NULL_TO_READY:
2707 case GST_STATE_CHANGE_READY_TO_PAUSED:
2708 gst_collect_pads_start (mux->collect);
2710 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
2712 case GST_STATE_CHANGE_PAUSED_TO_READY:
2713 gst_collect_pads_stop (mux->collect);
2719 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2721 switch (transition) {
2722 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
2724 case GST_STATE_CHANGE_PAUSED_TO_READY:
2725 gst_matroska_mux_reset (GST_ELEMENT (mux));
2727 case GST_STATE_CHANGE_READY_TO_NULL:
2737 gst_matroska_mux_set_property (GObject * object,
2738 guint prop_id, const GValue * value, GParamSpec * pspec)
2740 GstMatroskaMux *mux;
2742 g_return_if_fail (GST_IS_MATROSKA_MUX (object));
2743 mux = GST_MATROSKA_MUX (object);
2746 case ARG_WRITING_APP:
2747 if (!g_value_get_string (value)) {
2748 GST_WARNING_OBJECT (mux, "writing-app property can not be NULL");
2751 g_free (mux->writing_app);
2752 mux->writing_app = g_value_dup_string (value);
2754 case ARG_MATROSKA_VERSION:
2755 mux->matroska_version = g_value_get_int (value);
2757 case ARG_MIN_INDEX_INTERVAL:
2758 mux->min_index_interval = g_value_get_int64 (value);
2761 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2767 gst_matroska_mux_get_property (GObject * object,
2768 guint prop_id, GValue * value, GParamSpec * pspec)
2770 GstMatroskaMux *mux;
2772 g_return_if_fail (GST_IS_MATROSKA_MUX (object));
2773 mux = GST_MATROSKA_MUX (object);
2776 case ARG_WRITING_APP:
2777 g_value_set_string (value, mux->writing_app);
2779 case ARG_MATROSKA_VERSION:
2780 g_value_set_int (value, mux->matroska_version);
2782 case ARG_MIN_INDEX_INTERVAL:
2783 g_value_set_int64 (value, mux->min_index_interval);
2786 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2792 gst_matroska_mux_plugin_init (GstPlugin * plugin)
2794 return gst_element_register (plugin, "matroskamux",
2795 GST_RANK_PRIMARY, GST_TYPE_MATROSKA_MUX);