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 GType gst_matroska_mux_get_type (void);
203 GST_BOILERPLATE_FULL (GstMatroskaMux, gst_matroska_mux, GstElement,
204 GST_TYPE_ELEMENT, gst_matroska_mux_add_interfaces);
206 /* Matroska muxer destructor */
207 static void gst_matroska_mux_finalize (GObject * object);
209 /* Pads collected callback */
211 gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data);
214 static gboolean gst_matroska_mux_handle_src_event (GstPad * pad,
216 static GstPad *gst_matroska_mux_request_new_pad (GstElement * element,
217 GstPadTemplate * templ, const gchar * name);
218 static void gst_matroska_mux_release_pad (GstElement * element, GstPad * pad);
220 /* gst internal change state handler */
221 static GstStateChangeReturn
222 gst_matroska_mux_change_state (GstElement * element, GstStateChange transition);
224 /* gobject bla bla */
225 static void gst_matroska_mux_set_property (GObject * object,
226 guint prop_id, const GValue * value, GParamSpec * pspec);
227 static void gst_matroska_mux_get_property (GObject * object,
228 guint prop_id, GValue * value, GParamSpec * pspec);
231 static void gst_matroska_mux_reset (GstElement * element);
234 static guint64 gst_matroska_mux_create_uid ();
236 static gboolean theora_streamheader_to_codecdata (const GValue * streamheader,
237 GstMatroskaTrackContext * context);
238 static gboolean vorbis_streamheader_to_codecdata (const GValue * streamheader,
239 GstMatroskaTrackContext * context);
240 static gboolean speex_streamheader_to_codecdata (const GValue * streamheader,
241 GstMatroskaTrackContext * context);
242 static gboolean kate_streamheader_to_codecdata (const GValue * streamheader,
243 GstMatroskaTrackContext * context);
244 static gboolean flac_streamheader_to_codecdata (const GValue * streamheader,
245 GstMatroskaTrackContext * context);
248 gst_matroska_mux_add_interfaces (GType type)
250 static const GInterfaceInfo tag_setter_info = { NULL, NULL, NULL };
252 g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
256 gst_matroska_mux_base_init (gpointer g_class)
258 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
260 gst_element_class_add_pad_template (element_class,
261 gst_static_pad_template_get (&videosink_templ));
262 gst_element_class_add_pad_template (element_class,
263 gst_static_pad_template_get (&audiosink_templ));
264 gst_element_class_add_pad_template (element_class,
265 gst_static_pad_template_get (&subtitlesink_templ));
266 gst_element_class_add_pad_template (element_class,
267 gst_static_pad_template_get (&src_templ));
268 gst_element_class_set_details_simple (element_class, "Matroska muxer",
270 "Muxes video/audio/subtitle streams into a matroska stream",
271 "GStreamer maintainers <gstreamer-devel@lists.sourceforge.net>");
273 GST_DEBUG_CATEGORY_INIT (matroskamux_debug, "matroskamux", 0,
278 gst_matroska_mux_class_init (GstMatroskaMuxClass * klass)
280 GObjectClass *gobject_class;
281 GstElementClass *gstelement_class;
283 gobject_class = (GObjectClass *) klass;
284 gstelement_class = (GstElementClass *) klass;
286 gobject_class->finalize = gst_matroska_mux_finalize;
288 gobject_class->get_property = gst_matroska_mux_get_property;
289 gobject_class->set_property = gst_matroska_mux_set_property;
291 g_object_class_install_property (gobject_class, ARG_WRITING_APP,
292 g_param_spec_string ("writing-app", "Writing application.",
293 "The name the application that creates the matroska file.",
294 NULL, G_PARAM_READWRITE));
295 g_object_class_install_property (gobject_class, ARG_MATROSKA_VERSION,
296 g_param_spec_int ("version", "Matroska version",
297 "This parameter determines what matroska features can be used.",
298 1, 2, DEFAULT_MATROSKA_VERSION, G_PARAM_READWRITE));
299 g_object_class_install_property (gobject_class, ARG_MIN_INDEX_INTERVAL,
300 g_param_spec_int64 ("min-index-interval", "Minimum time between index "
301 "entries", "An index entry is created every so many nanoseconds.",
302 0, G_MAXINT64, DEFAULT_MIN_INDEX_INTERVAL, G_PARAM_READWRITE));
304 gstelement_class->change_state =
305 GST_DEBUG_FUNCPTR (gst_matroska_mux_change_state);
306 gstelement_class->request_new_pad =
307 GST_DEBUG_FUNCPTR (gst_matroska_mux_request_new_pad);
308 gstelement_class->release_pad =
309 GST_DEBUG_FUNCPTR (gst_matroska_mux_release_pad);
314 * gst_matroska_mux_init:
315 * @mux: #GstMatroskaMux that should be initialized.
316 * @g_class: Class of the muxer.
318 * Matroska muxer constructor.
321 gst_matroska_mux_init (GstMatroskaMux * mux, GstMatroskaMuxClass * g_class)
323 mux->srcpad = gst_pad_new_from_static_template (&src_templ, "src");
324 gst_pad_set_event_function (mux->srcpad, gst_matroska_mux_handle_src_event);
325 gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad);
327 mux->collect = gst_collect_pads_new ();
328 gst_collect_pads_set_function (mux->collect,
329 (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_matroska_mux_collected),
332 mux->ebml_write = gst_ebml_write_new (mux->srcpad);
334 /* property defaults */
335 mux->matroska_version = DEFAULT_MATROSKA_VERSION;
336 mux->writing_app = g_strdup (DEFAULT_WRITING_APP);
337 mux->min_index_interval = DEFAULT_MIN_INDEX_INTERVAL;
339 /* initialize internal variables */
341 mux->num_streams = 0;
342 mux->num_a_streams = 0;
343 mux->num_t_streams = 0;
344 mux->num_v_streams = 0;
346 /* initialize remaining variables */
347 gst_matroska_mux_reset (GST_ELEMENT (mux));
352 * gst_matroska_mux_finalize:
353 * @object: #GstMatroskaMux that should be finalized.
355 * Finalize matroska muxer.
358 gst_matroska_mux_finalize (GObject * object)
360 GstMatroskaMux *mux = GST_MATROSKA_MUX (object);
362 gst_object_unref (mux->collect);
363 gst_object_unref (mux->ebml_write);
364 if (mux->writing_app)
365 g_free (mux->writing_app);
367 G_OBJECT_CLASS (parent_class)->finalize (object);
372 * gst_matroska_mux_create_uid:
374 * Generate new unused track UID.
376 * Returns: New track UID.
379 gst_matroska_mux_create_uid (void)
386 used_uids = g_array_sized_new (FALSE, FALSE, sizeof (guint64), 10);
391 uid = (((guint64) g_random_int ()) << 32) | g_random_int ();
392 for (i = 0; i < used_uids->len; i++) {
393 if (g_array_index (used_uids, guint64, i) == uid) {
398 g_array_append_val (used_uids, uid);
401 G_UNLOCK (used_uids);
407 * gst_matroska_pad_reset:
408 * @collect_pad: the #GstMatroskaPad
410 * Reset and/or release resources of a matroska collect pad.
413 gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full)
416 GstMatroskaTrackType type = 0;
418 /* free track information */
419 if (collect_pad->track != NULL) {
420 /* retrieve for optional later use */
421 name = collect_pad->track->name;
422 type = collect_pad->track->type;
423 /* extra for video */
424 if (type == GST_MATROSKA_TRACK_TYPE_VIDEO) {
425 GstMatroskaTrackVideoContext *ctx =
426 (GstMatroskaTrackVideoContext *) collect_pad->track;
428 if (ctx->dirac_unit) {
429 gst_buffer_unref (ctx->dirac_unit);
430 ctx->dirac_unit = NULL;
433 g_free (collect_pad->track->codec_id);
434 g_free (collect_pad->track->codec_name);
436 g_free (collect_pad->track->name);
437 g_free (collect_pad->track->language);
438 g_free (collect_pad->track->codec_priv);
439 g_free (collect_pad->track);
440 collect_pad->track = NULL;
443 /* free cached buffer */
444 if (collect_pad->buffer != NULL) {
445 gst_buffer_unref (collect_pad->buffer);
446 collect_pad->buffer = NULL;
449 if (!full && type != 0) {
450 GstMatroskaTrackContext *context;
452 /* create a fresh context */
454 case GST_MATROSKA_TRACK_TYPE_VIDEO:
455 context = (GstMatroskaTrackContext *)
456 g_new0 (GstMatroskaTrackVideoContext, 1);
458 case GST_MATROSKA_TRACK_TYPE_AUDIO:
459 context = (GstMatroskaTrackContext *)
460 g_new0 (GstMatroskaTrackAudioContext, 1);
462 case GST_MATROSKA_TRACK_TYPE_SUBTITLE:
463 context = (GstMatroskaTrackContext *)
464 g_new0 (GstMatroskaTrackSubtitleContext, 1);
467 g_assert_not_reached ();
471 context->type = type;
472 context->name = name;
473 /* TODO: check default values for the context */
474 context->flags = GST_MATROSKA_TRACK_ENABLED | GST_MATROSKA_TRACK_DEFAULT;
475 collect_pad->track = context;
476 collect_pad->buffer = NULL;
477 collect_pad->duration = 0;
478 collect_pad->start_ts = GST_CLOCK_TIME_NONE;
479 collect_pad->end_ts = GST_CLOCK_TIME_NONE;
484 * gst_matroska_pad_free:
485 * @collect_pad: the #GstMatroskaPad
487 * Release resources of a matroska collect pad.
490 gst_matroska_pad_free (GstMatroskaPad * collect_pad)
492 gst_matroska_pad_reset (collect_pad, TRUE);
497 * gst_matroska_mux_reset:
498 * @element: #GstMatroskaMux that should be reseted.
500 * Reset matroska muxer back to initial state.
503 gst_matroska_mux_reset (GstElement * element)
505 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
508 /* reset EBML write */
509 gst_ebml_write_reset (mux->ebml_write);
512 mux->state = GST_MATROSKA_MUX_STATE_START;
514 /* clean up existing streams */
516 for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
517 GstMatroskaPad *collect_pad;
519 collect_pad = (GstMatroskaPad *) walk->data;
521 /* reset collect pad to pristine state */
522 gst_matroska_pad_reset (collect_pad, FALSE);
526 mux->num_indexes = 0;
531 mux->time_scale = GST_MSECOND;
536 mux->cluster_time = 0;
537 mux->cluster_pos = 0;
540 gst_tag_setter_reset_tags (GST_TAG_SETTER (mux));
544 * gst_matroska_mux_handle_src_event:
545 * @pad: Pad which received the event.
546 * @event: Received event.
548 * handle events - copied from oggmux without understanding
550 * Returns: #TRUE on success.
553 gst_matroska_mux_handle_src_event (GstPad * pad, GstEvent * event)
557 type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;
561 /* disable seeking for now */
567 return gst_pad_event_default (pad, event);
571 * gst_matroska_mux_handle_sink_event:
572 * @pad: Pad which received the event.
573 * @event: Received event.
575 * handle events - informational ones like tags
577 * Returns: #TRUE on success.
580 gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event)
582 GstMatroskaTrackContext *context;
583 GstMatroskaPad *collect_pad;
588 mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad));
590 /* FIXME: aren't we either leaking events here or doing a wrong unref? */
591 switch (GST_EVENT_TYPE (event)) {
595 GST_DEBUG_OBJECT (mux, "received tag event");
596 gst_event_parse_tag (event, &list);
598 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
599 g_assert (collect_pad);
600 context = collect_pad->track;
603 /* Matroska wants ISO 639-2B code, taglist most likely contains 639-1 */
604 if (gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &lang)) {
605 const gchar *lang_code;
607 lang_code = gst_tag_get_language_code_iso_639_2B (lang);
609 GST_INFO_OBJECT (pad, "Setting language to '%s'", lang_code);
610 context->language = g_strdup (lang_code);
612 GST_WARNING_OBJECT (pad, "Did not get language code for '%s'", lang);
617 gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), list,
618 gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (mux)));
621 case GST_EVENT_NEWSEGMENT:
622 /* We don't support NEWSEGMENT events */
624 gst_event_unref (event);
630 /* now GstCollectPads can take care of the rest, e.g. EOS */
632 ret = mux->collect_event (pad, event);
633 gst_object_unref (mux);
640 * gst_matroska_mux_video_pad_setcaps:
641 * @pad: Pad which got the caps.
644 * Setcaps function for video sink pad.
646 * Returns: #TRUE on success.
649 gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
651 GstMatroskaTrackContext *context = NULL;
652 GstMatroskaTrackVideoContext *videocontext;
654 GstMatroskaPad *collect_pad;
655 GstStructure *structure;
656 const gchar *mimetype;
657 const GValue *value = NULL;
658 const GstBuffer *codec_buf = NULL;
659 gint width, height, pixel_width, pixel_height;
662 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
665 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
666 g_assert (collect_pad);
667 context = collect_pad->track;
669 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO);
670 videocontext = (GstMatroskaTrackVideoContext *) context;
672 /* gst -> matroska ID'ing */
673 structure = gst_caps_get_structure (caps, 0);
675 mimetype = gst_structure_get_name (structure);
677 if (!strcmp (mimetype, "video/x-theora")) {
678 /* we'll extract the details later from the theora identification header */
682 /* get general properties */
683 /* spec says it is mandatory */
684 if (!gst_structure_get_int (structure, "width", &width) ||
685 !gst_structure_get_int (structure, "height", &height))
688 videocontext->pixel_width = width;
689 videocontext->pixel_height = height;
690 if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d)
692 context->default_duration =
693 gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n);
694 GST_LOG_OBJECT (pad, "default duration = %" GST_TIME_FORMAT,
695 GST_TIME_ARGS (context->default_duration));
697 context->default_duration = 0;
699 if (gst_structure_get_fraction (structure, "pixel-aspect-ratio",
700 &pixel_width, &pixel_height)) {
701 if (pixel_width > pixel_height) {
702 videocontext->display_width = width * pixel_width / pixel_height;
703 videocontext->display_height = height;
704 } else if (pixel_width < pixel_height) {
705 videocontext->display_width = width;
706 videocontext->display_height = height * pixel_height / pixel_width;
708 videocontext->display_width = 0;
709 videocontext->display_height = 0;
712 videocontext->display_width = 0;
713 videocontext->display_height = 0;
718 videocontext->asr_mode = GST_MATROSKA_ASPECT_RATIO_MODE_FREE;
719 videocontext->fourcc = 0;
721 /* TODO: - check if we handle all codecs by the spec, i.e. codec private
722 * data and other settings
726 /* extract codec_data, may turn out needed */
727 value = gst_structure_get_value (structure, "codec_data");
729 codec_buf = gst_value_get_buffer (value);
732 if (!strcmp (mimetype, "video/x-raw-yuv")) {
733 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED);
734 gst_structure_get_fourcc (structure, "format", &videocontext->fourcc);
735 } else if (!strcmp (mimetype, "image/jpeg")) {
736 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
737 } else if (!strcmp (mimetype, "video/x-xvid") /* MS/VfW compatibility cases */
738 ||!strcmp (mimetype, "video/x-huffyuv")
739 || !strcmp (mimetype, "video/x-divx")
740 || !strcmp (mimetype, "video/x-dv")
741 || !strcmp (mimetype, "video/x-h263")
742 || !strcmp (mimetype, "video/x-msmpeg")
743 || !strcmp (mimetype, "video/x-wmv")) {
744 BITMAPINFOHEADER *bih;
745 gint size = sizeof (BITMAPINFOHEADER);
748 if (!strcmp (mimetype, "video/x-xvid"))
749 fourcc = GST_MAKE_FOURCC ('X', 'V', 'I', 'D');
750 else if (!strcmp (mimetype, "video/x-huffyuv"))
751 fourcc = GST_MAKE_FOURCC ('H', 'F', 'Y', 'U');
752 else if (!strcmp (mimetype, "video/x-dv"))
753 fourcc = GST_MAKE_FOURCC ('D', 'V', 'S', 'D');
754 else if (!strcmp (mimetype, "video/x-h263"))
755 fourcc = GST_MAKE_FOURCC ('H', '2', '6', '3');
756 else if (!strcmp (mimetype, "video/x-divx")) {
759 gst_structure_get_int (structure, "divxversion", &divxversion);
760 switch (divxversion) {
762 fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', '3');
765 fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', 'X');
768 fourcc = GST_MAKE_FOURCC ('D', 'X', '5', '0');
771 } else if (!strcmp (mimetype, "video/x-msmpeg")) {
774 gst_structure_get_int (structure, "msmpegversion", &msmpegversion);
775 switch (msmpegversion) {
777 fourcc = GST_MAKE_FOURCC ('M', 'P', 'G', '4');
780 fourcc = GST_MAKE_FOURCC ('M', 'P', '4', '2');
786 } else if (!strcmp (mimetype, "video/x-wmv")) {
789 if (gst_structure_get_fourcc (structure, "format", &format)) {
791 } else if (gst_structure_get_int (structure, "wmvversion", &wmvversion)) {
792 if (wmvversion == 2) {
793 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '2');
794 } else if (wmvversion == 1) {
795 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '1');
796 } else if (wmvversion == 3) {
797 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '3');
805 bih = g_new0 (BITMAPINFOHEADER, 1);
806 GST_WRITE_UINT32_LE (&bih->bi_size, size);
807 GST_WRITE_UINT32_LE (&bih->bi_width, videocontext->pixel_width);
808 GST_WRITE_UINT32_LE (&bih->bi_height, videocontext->pixel_height);
809 GST_WRITE_UINT32_LE (&bih->bi_compression, fourcc);
810 GST_WRITE_UINT16_LE (&bih->bi_planes, (guint16) 1);
811 GST_WRITE_UINT16_LE (&bih->bi_bit_count, (guint16) 24);
812 GST_WRITE_UINT32_LE (&bih->bi_size_image, videocontext->pixel_width *
813 videocontext->pixel_height * 3);
815 /* process codec private/initialization data, if any */
817 size += GST_BUFFER_SIZE (codec_buf);
818 bih = g_realloc (bih, size);
819 GST_WRITE_UINT32_LE (&bih->bi_size, size);
820 memcpy ((guint8 *) bih + sizeof (BITMAPINFOHEADER),
821 GST_BUFFER_DATA (codec_buf), GST_BUFFER_SIZE (codec_buf));
824 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
825 context->codec_priv = (gpointer) bih;
826 context->codec_priv_size = size;
827 } else if (!strcmp (mimetype, "video/x-h264")) {
828 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC);
830 if (context->codec_priv != NULL) {
831 g_free (context->codec_priv);
832 context->codec_priv = NULL;
833 context->codec_priv_size = 0;
836 /* Create avcC header */
837 if (codec_buf != NULL) {
838 context->codec_priv_size = GST_BUFFER_SIZE (codec_buf);
839 context->codec_priv = g_malloc0 (context->codec_priv_size);
840 memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
841 context->codec_priv_size);
843 } else if (!strcmp (mimetype, "video/x-theora")) {
844 const GValue *streamheader;
846 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_THEORA);
848 if (context->codec_priv != NULL) {
849 g_free (context->codec_priv);
850 context->codec_priv = NULL;
851 context->codec_priv_size = 0;
854 streamheader = gst_structure_get_value (structure, "streamheader");
855 if (!theora_streamheader_to_codecdata (streamheader, context)) {
856 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
857 ("theora stream headers missing or malformed"));
860 } else if (!strcmp (mimetype, "video/x-dirac")) {
861 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_DIRAC);
862 } else if (!strcmp (mimetype, "video/mpeg")) {
865 gst_structure_get_int (structure, "mpegversion", &mpegversion);
866 switch (mpegversion) {
868 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG1);
871 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG2);
874 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
880 /* global headers may be in codec data */
881 if (codec_buf != NULL) {
882 context->codec_priv_size = GST_BUFFER_SIZE (codec_buf);
883 context->codec_priv = g_malloc0 (context->codec_priv_size);
884 memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
885 context->codec_priv_size);
887 } else if (!strcmp (mimetype, "video/x-msmpeg")) {
889 /* can only make it here if preceding case verified it was version 3 */
890 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3);
891 } else if (!strcmp (mimetype, "video/x-pn-realvideo")) {
893 const GValue *mdpr_data;
895 gst_structure_get_int (structure, "rmversion", &rmversion);
898 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1);
901 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2);
904 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3);
907 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4);
913 mdpr_data = gst_structure_get_value (structure, "mdpr_data");
914 if (mdpr_data != NULL) {
915 guint8 *priv_data = NULL;
916 guint priv_data_size = 0;
918 GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data);
920 priv_data_size = GST_BUFFER_SIZE (codec_data_buf);
921 priv_data = g_malloc0 (priv_data_size);
923 memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size);
925 context->codec_priv = priv_data;
926 context->codec_priv_size = priv_data_size;
935 GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT,
936 GST_PAD_NAME (pad), caps);
941 /* N > 0 to expect a particular number of headers, negative if the
942 number of headers is variable */
944 xiphN_streamheader_to_codecdata (const GValue * streamheader,
945 GstMatroskaTrackContext * context, GstBuffer ** p_buf0, int N)
947 GstBuffer **buf = NULL;
950 guint bufi, i, offset, priv_data_size;
952 if (streamheader == NULL)
953 goto no_stream_headers;
955 if (G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY)
958 bufarr = g_value_peek_pointer (streamheader);
959 if (bufarr->len <= 0 || bufarr->len > 255) /* at least one header, and count stored in a byte */
961 if (N > 0 && bufarr->len != N)
964 context->xiph_headers_to_skip = bufarr->len;
966 buf = (GstBuffer **) g_malloc0 (sizeof (GstBuffer *) * bufarr->len);
967 for (i = 0; i < bufarr->len; i++) {
968 GValue *bufval = &g_array_index (bufarr, GValue, i);
970 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
972 goto wrong_content_type;
975 buf[i] = g_value_peek_pointer (bufval);
979 if (bufarr->len > 0) {
980 for (i = 0; i < bufarr->len - 1; i++) {
981 priv_data_size += GST_BUFFER_SIZE (buf[i]) / 0xff + 1;
985 for (i = 0; i < bufarr->len; ++i) {
986 priv_data_size += GST_BUFFER_SIZE (buf[i]);
989 priv_data = g_malloc0 (priv_data_size);
991 priv_data[0] = bufarr->len - 1;
994 if (bufarr->len > 0) {
995 for (bufi = 0; bufi < bufarr->len - 1; bufi++) {
996 for (i = 0; i < GST_BUFFER_SIZE (buf[bufi]) / 0xff; ++i) {
997 priv_data[offset++] = 0xff;
999 priv_data[offset++] = GST_BUFFER_SIZE (buf[bufi]) % 0xff;
1003 for (i = 0; i < bufarr->len; ++i) {
1004 memcpy (priv_data + offset, GST_BUFFER_DATA (buf[i]),
1005 GST_BUFFER_SIZE (buf[i]));
1006 offset += GST_BUFFER_SIZE (buf[i]);
1009 context->codec_priv = priv_data;
1010 context->codec_priv_size = priv_data_size;
1013 *p_buf0 = gst_buffer_ref (buf[0]);
1022 GST_WARNING ("required streamheaders missing in sink caps!");
1027 GST_WARNING ("streamheaders are not a GST_TYPE_ARRAY, but a %s",
1028 G_VALUE_TYPE_NAME (streamheader));
1033 GST_WARNING ("got %u streamheaders, not %d as expected", bufarr->len, N);
1038 GST_WARNING ("streamheaders array does not contain GstBuffers");
1044 vorbis_streamheader_to_codecdata (const GValue * streamheader,
1045 GstMatroskaTrackContext * context)
1047 GstBuffer *buf0 = NULL;
1049 if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3))
1052 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 4) {
1053 GST_WARNING ("First vorbis header too small, ignoring");
1055 if (memcmp (GST_BUFFER_DATA (buf0) + 1, "vorbis", 6) == 0) {
1056 GstMatroskaTrackAudioContext *audiocontext;
1059 hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 4;
1060 audiocontext = (GstMatroskaTrackAudioContext *) context;
1061 audiocontext->channels = GST_READ_UINT8 (hdr);
1062 audiocontext->samplerate = GST_READ_UINT32_LE (hdr + 1);
1067 gst_buffer_unref (buf0);
1073 theora_streamheader_to_codecdata (const GValue * streamheader,
1074 GstMatroskaTrackContext * context)
1076 GstBuffer *buf0 = NULL;
1078 if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3))
1081 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 26) {
1082 GST_WARNING ("First theora header too small, ignoring");
1083 } else if (memcmp (GST_BUFFER_DATA (buf0), "\200theora\003\002", 9) != 0) {
1084 GST_WARNING ("First header not a theora identification header, ignoring");
1086 GstMatroskaTrackVideoContext *videocontext;
1087 guint fps_num, fps_denom, par_num, par_denom;
1090 hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 3 + 2 + 2;
1092 videocontext = (GstMatroskaTrackVideoContext *) context;
1093 videocontext->pixel_width = GST_READ_UINT32_BE (hdr) >> 8;
1094 videocontext->pixel_height = GST_READ_UINT32_BE (hdr + 3) >> 8;
1095 hdr += 3 + 3 + 1 + 1;
1096 fps_num = GST_READ_UINT32_BE (hdr);
1097 fps_denom = GST_READ_UINT32_BE (hdr + 4);
1098 context->default_duration = gst_util_uint64_scale_int (GST_SECOND,
1099 fps_denom, fps_num);
1101 par_num = GST_READ_UINT32_BE (hdr) >> 8;
1102 par_denom = GST_READ_UINT32_BE (hdr + 3) >> 8;
1103 if (par_num > 0 && par_num > 0) {
1104 if (par_num > par_denom) {
1105 videocontext->display_width =
1106 videocontext->pixel_width * par_num / par_denom;
1107 videocontext->display_height = videocontext->pixel_height;
1108 } else if (par_num < par_denom) {
1109 videocontext->display_width = videocontext->pixel_width;
1110 videocontext->display_height =
1111 videocontext->pixel_height * par_denom / par_num;
1113 videocontext->display_width = 0;
1114 videocontext->display_height = 0;
1117 videocontext->display_width = 0;
1118 videocontext->display_height = 0;
1124 gst_buffer_unref (buf0);
1130 kate_streamheader_to_codecdata (const GValue * streamheader,
1131 GstMatroskaTrackContext * context)
1133 GstBuffer *buf0 = NULL;
1135 if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, -1))
1138 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 64) { /* Kate ID header is 64 bytes */
1139 GST_WARNING ("First kate header too small, ignoring");
1140 } else if (memcmp (GST_BUFFER_DATA (buf0), "\200kate\0\0\0", 8) != 0) {
1141 GST_WARNING ("First header not a kate identification header, ignoring");
1145 gst_buffer_unref (buf0);
1151 flac_streamheader_to_codecdata (const GValue * streamheader,
1152 GstMatroskaTrackContext * context)
1159 if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) {
1160 GST_WARNING ("No or invalid streamheader field in the caps");
1164 bufarr = g_value_peek_pointer (streamheader);
1165 if (bufarr->len < 2) {
1166 GST_WARNING ("Too few headers in streamheader field");
1170 context->xiph_headers_to_skip = bufarr->len + 1;
1172 bufval = &g_array_index (bufarr, GValue, 0);
1173 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1174 GST_WARNING ("streamheaders array does not contain GstBuffers");
1178 buffer = g_value_peek_pointer (bufval);
1180 /* Need at least OggFLAC mapping header, fLaC marker and STREAMINFO block */
1181 if (GST_BUFFER_SIZE (buffer) < 9 + 4 + 4 + 34
1182 || memcmp (GST_BUFFER_DATA (buffer) + 1, "FLAC", 4) != 0
1183 || memcmp (GST_BUFFER_DATA (buffer) + 9, "fLaC", 4) != 0) {
1184 GST_WARNING ("Invalid streamheader for FLAC");
1188 context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer) - 9);
1189 context->codec_priv_size = GST_BUFFER_SIZE (buffer) - 9;
1190 memcpy (context->codec_priv, GST_BUFFER_DATA (buffer) + 9,
1191 GST_BUFFER_SIZE (buffer) - 9);
1193 for (i = 1; i < bufarr->len; i++) {
1194 bufval = &g_array_index (bufarr, GValue, i);
1196 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1197 g_free (context->codec_priv);
1198 context->codec_priv = NULL;
1199 context->codec_priv_size = 0;
1200 GST_WARNING ("streamheaders array does not contain GstBuffers");
1204 buffer = g_value_peek_pointer (bufval);
1206 context->codec_priv =
1207 g_realloc (context->codec_priv,
1208 context->codec_priv_size + GST_BUFFER_SIZE (buffer));
1209 memcpy ((guint8 *) context->codec_priv + context->codec_priv_size,
1210 GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
1211 context->codec_priv_size =
1212 context->codec_priv_size + GST_BUFFER_SIZE (buffer);
1219 speex_streamheader_to_codecdata (const GValue * streamheader,
1220 GstMatroskaTrackContext * context)
1226 if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) {
1227 GST_WARNING ("No or invalid streamheader field in the caps");
1231 bufarr = g_value_peek_pointer (streamheader);
1232 if (bufarr->len != 2) {
1233 GST_WARNING ("Too few headers in streamheader field");
1237 context->xiph_headers_to_skip = bufarr->len + 1;
1239 bufval = &g_array_index (bufarr, GValue, 0);
1240 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1241 GST_WARNING ("streamheaders array does not contain GstBuffers");
1245 buffer = g_value_peek_pointer (bufval);
1247 if (GST_BUFFER_SIZE (buffer) < 80
1248 || memcmp (GST_BUFFER_DATA (buffer), "Speex ", 8) != 0) {
1249 GST_WARNING ("Invalid streamheader for Speex");
1253 context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer));
1254 context->codec_priv_size = GST_BUFFER_SIZE (buffer);
1255 memcpy (context->codec_priv, GST_BUFFER_DATA (buffer),
1256 GST_BUFFER_SIZE (buffer));
1258 bufval = &g_array_index (bufarr, GValue, 1);
1260 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1261 g_free (context->codec_priv);
1262 context->codec_priv = NULL;
1263 context->codec_priv_size = 0;
1264 GST_WARNING ("streamheaders array does not contain GstBuffers");
1268 buffer = g_value_peek_pointer (bufval);
1270 context->codec_priv =
1271 g_realloc (context->codec_priv,
1272 context->codec_priv_size + GST_BUFFER_SIZE (buffer));
1273 memcpy ((guint8 *) context->codec_priv + context->codec_priv_size,
1274 GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
1275 context->codec_priv_size =
1276 context->codec_priv_size + GST_BUFFER_SIZE (buffer);
1282 aac_codec_data_to_codec_id (const GstBuffer * buf)
1287 /* default to MAIN */
1290 if (GST_BUFFER_SIZE (buf) >= 2) {
1291 profile = GST_READ_UINT8 (GST_BUFFER_DATA (buf));
1309 GST_WARNING ("unknown AAC profile, defaulting to MAIN");
1318 * gst_matroska_mux_audio_pad_setcaps:
1319 * @pad: Pad which got the caps.
1322 * Setcaps function for audio sink pad.
1324 * Returns: #TRUE on success.
1327 gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
1329 GstMatroskaTrackContext *context = NULL;
1330 GstMatroskaTrackAudioContext *audiocontext;
1331 GstMatroskaMux *mux;
1332 GstMatroskaPad *collect_pad;
1333 const gchar *mimetype;
1334 gint samplerate = 0, channels = 0;
1335 GstStructure *structure;
1336 const GValue *codec_data = NULL;
1337 const GstBuffer *buf = NULL;
1338 const gchar *stream_format = NULL;
1340 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1343 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
1344 g_assert (collect_pad);
1345 context = collect_pad->track;
1347 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_AUDIO);
1348 audiocontext = (GstMatroskaTrackAudioContext *) context;
1350 structure = gst_caps_get_structure (caps, 0);
1351 mimetype = gst_structure_get_name (structure);
1354 gst_structure_get_int (structure, "rate", &samplerate);
1355 gst_structure_get_int (structure, "channels", &channels);
1357 audiocontext->samplerate = samplerate;
1358 audiocontext->channels = channels;
1359 audiocontext->bitdepth = 0;
1360 context->default_duration = 0;
1362 codec_data = gst_structure_get_value (structure, "codec_data");
1364 buf = gst_value_get_buffer (codec_data);
1366 /* TODO: - check if we handle all codecs by the spec, i.e. codec private
1367 * data and other settings
1371 if (!strcmp (mimetype, "audio/mpeg")) {
1372 gint mpegversion = 0;
1374 gst_structure_get_int (structure, "mpegversion", &mpegversion);
1375 switch (mpegversion) {
1381 gst_structure_get_int (structure, "layer", &layer);
1383 if (!gst_structure_get_int (structure, "mpegaudioversion", &version)) {
1384 GST_WARNING_OBJECT (mux,
1385 "Unable to determine MPEG audio version, assuming 1");
1391 else if (layer == 2)
1393 else if (version == 2)
1398 context->default_duration =
1399 gst_util_uint64_scale (GST_SECOND, spf, audiocontext->samplerate);
1403 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1);
1406 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2);
1409 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3);
1418 stream_format = gst_structure_get_string (structure, "stream-format");
1419 /* check this is raw aac */
1420 if (stream_format) {
1421 if (strcmp (stream_format, "raw") != 0) {
1422 GST_WARNING_OBJECT (mux, "AAC stream-format must be 'raw', not %s",
1426 GST_WARNING_OBJECT (mux, "AAC stream-format not specified, "
1431 if (mpegversion == 2)
1433 g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG2 "%s",
1434 aac_codec_data_to_codec_id (buf));
1435 else if (mpegversion == 4)
1437 g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4 "%s",
1438 aac_codec_data_to_codec_id (buf));
1440 g_assert_not_reached ();
1442 GST_DEBUG_OBJECT (mux, "no AAC codec_data; not packetized");
1449 } else if (!strcmp (mimetype, "audio/x-raw-int")) {
1451 gint endianness = G_LITTLE_ENDIAN;
1452 gboolean signedness = TRUE;
1454 if (!gst_structure_get_int (structure, "width", &width) ||
1455 !gst_structure_get_int (structure, "depth", &depth) ||
1456 !gst_structure_get_boolean (structure, "signed", &signedness)) {
1457 GST_DEBUG_OBJECT (mux, "broken caps, width/depth/signed field missing");
1462 !gst_structure_get_int (structure, "endianness", &endianness)) {
1463 GST_DEBUG_OBJECT (mux, "broken caps, no endianness specified");
1467 if (width != depth) {
1468 GST_DEBUG_OBJECT (mux, "width must be same as depth!");
1472 /* FIXME: where is this spec'ed out? (tpm) */
1473 if ((width == 8 && signedness) || (width >= 16 && !signedness)) {
1474 GST_DEBUG_OBJECT (mux, "8-bit PCM must be unsigned, 16-bit PCM signed");
1478 audiocontext->bitdepth = depth;
1479 if (endianness == G_BIG_ENDIAN)
1480 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE);
1482 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE);
1484 } else if (!strcmp (mimetype, "audio/x-raw-float")) {
1487 if (!gst_structure_get_int (structure, "width", &width)) {
1488 GST_DEBUG_OBJECT (mux, "broken caps, width field missing");
1492 audiocontext->bitdepth = width;
1493 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT);
1495 } else if (!strcmp (mimetype, "audio/x-vorbis")) {
1496 const GValue *streamheader;
1498 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_VORBIS);
1500 if (context->codec_priv != NULL) {
1501 g_free (context->codec_priv);
1502 context->codec_priv = NULL;
1503 context->codec_priv_size = 0;
1506 streamheader = gst_structure_get_value (structure, "streamheader");
1507 if (!vorbis_streamheader_to_codecdata (streamheader, context)) {
1508 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1509 ("vorbis stream headers missing or malformed"));
1512 } else if (!strcmp (mimetype, "audio/x-flac")) {
1513 const GValue *streamheader;
1515 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_FLAC);
1516 if (context->codec_priv != NULL) {
1517 g_free (context->codec_priv);
1518 context->codec_priv = NULL;
1519 context->codec_priv_size = 0;
1522 streamheader = gst_structure_get_value (structure, "streamheader");
1523 if (!flac_streamheader_to_codecdata (streamheader, context)) {
1524 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1525 ("flac stream headers missing or malformed"));
1528 } else if (!strcmp (mimetype, "audio/x-speex")) {
1529 const GValue *streamheader;
1531 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_SPEEX);
1532 if (context->codec_priv != NULL) {
1533 g_free (context->codec_priv);
1534 context->codec_priv = NULL;
1535 context->codec_priv_size = 0;
1538 streamheader = gst_structure_get_value (structure, "streamheader");
1539 if (!speex_streamheader_to_codecdata (streamheader, context)) {
1540 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1541 ("speex stream headers missing or malformed"));
1544 } else if (!strcmp (mimetype, "audio/x-ac3")) {
1545 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3);
1546 } else if (!strcmp (mimetype, "audio/x-tta")) {
1549 /* TTA frame duration */
1550 context->default_duration = 1.04489795918367346939 * GST_SECOND;
1552 gst_structure_get_int (structure, "width", &width);
1553 audiocontext->bitdepth = width;
1554 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_TTA);
1556 } else if (!strcmp (mimetype, "audio/x-pn-realaudio")) {
1558 const GValue *mdpr_data;
1560 gst_structure_get_int (structure, "raversion", &raversion);
1561 switch (raversion) {
1563 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4);
1566 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_28_8);
1569 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK);
1575 mdpr_data = gst_structure_get_value (structure, "mdpr_data");
1576 if (mdpr_data != NULL) {
1577 guint8 *priv_data = NULL;
1578 guint priv_data_size = 0;
1580 GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data);
1582 priv_data_size = GST_BUFFER_SIZE (codec_data_buf);
1583 priv_data = g_malloc0 (priv_data_size);
1585 memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size);
1587 context->codec_priv = priv_data;
1588 context->codec_priv_size = priv_data_size;
1591 } else if (!strcmp (mimetype, "audio/x-wma")) {
1593 guint codec_priv_size;
1600 if (!gst_structure_get_int (structure, "wmaversion", &wmaversion)
1601 || !gst_structure_get_int (structure, "block_align", &block_align)
1602 || !gst_structure_get_int (structure, "bitrate", &bitrate)
1603 || samplerate == 0 || channels == 0) {
1604 GST_WARNING_OBJECT (mux, "Missing wmaversion/block_align/bitrate/"
1605 "channels/rate on WMA caps");
1609 switch (wmaversion) {
1611 format = GST_RIFF_WAVE_FORMAT_WMAV1;
1614 format = GST_RIFF_WAVE_FORMAT_WMAV2;
1617 format = GST_RIFF_WAVE_FORMAT_WMAV3;
1620 GST_WARNING_OBJECT (mux, "Unexpected WMA version: %d", wmaversion);
1624 if (gst_structure_get_int (structure, "depth", &depth))
1625 audiocontext->bitdepth = depth;
1627 codec_priv_size = WAVEFORMATEX_SIZE;
1629 codec_priv_size += GST_BUFFER_SIZE (buf);
1631 /* serialize waveformatex structure */
1632 codec_priv = g_malloc0 (codec_priv_size);
1633 GST_WRITE_UINT16_LE (codec_priv, format);
1634 GST_WRITE_UINT16_LE (codec_priv + 2, channels);
1635 GST_WRITE_UINT32_LE (codec_priv + 4, samplerate);
1636 GST_WRITE_UINT32_LE (codec_priv + 8, bitrate / 8);
1637 GST_WRITE_UINT16_LE (codec_priv + 12, block_align);
1638 GST_WRITE_UINT16_LE (codec_priv + 14, 0);
1640 GST_WRITE_UINT16_LE (codec_priv + 16, GST_BUFFER_SIZE (buf));
1642 GST_WRITE_UINT16_LE (codec_priv + 16, 0);
1644 /* process codec private/initialization data, if any */
1646 memcpy ((guint8 *) codec_priv + WAVEFORMATEX_SIZE,
1647 GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
1650 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_ACM);
1651 context->codec_priv = (gpointer) codec_priv;
1652 context->codec_priv_size = codec_priv_size;
1660 GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT,
1661 GST_PAD_NAME (pad), caps);
1668 * gst_matroska_mux_subtitle_pad_setcaps:
1669 * @pad: Pad which got the caps.
1672 * Setcaps function for subtitle sink pad.
1674 * Returns: #TRUE on success.
1677 gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps)
1680 * Consider this as boilerplate code for now. There is
1681 * no single subtitle creation element in GStreamer,
1682 * neither do I know how subtitling works at all. */
1684 /* There is now (at least) one such alement (kateenc), and I'm going
1685 to handle it here and claim it works when it can be piped back
1686 through GStreamer and VLC */
1688 GstMatroskaTrackContext *context = NULL;
1689 GstMatroskaTrackSubtitleContext *scontext;
1690 GstMatroskaMux *mux;
1691 GstMatroskaPad *collect_pad;
1692 const gchar *mimetype;
1693 GstStructure *structure;
1695 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1698 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
1699 g_assert (collect_pad);
1700 context = collect_pad->track;
1702 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_SUBTITLE);
1703 scontext = (GstMatroskaTrackSubtitleContext *) context;
1705 structure = gst_caps_get_structure (caps, 0);
1706 mimetype = gst_structure_get_name (structure);
1709 scontext->check_utf8 = 1;
1710 scontext->invalid_utf8 = 0;
1711 context->default_duration = 0;
1713 /* TODO: - other format than Kate */
1715 if (!strcmp (mimetype, "subtitle/x-kate")) {
1716 const GValue *streamheader;
1718 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_SUBTITLE_KATE);
1720 if (context->codec_priv != NULL) {
1721 g_free (context->codec_priv);
1722 context->codec_priv = NULL;
1723 context->codec_priv_size = 0;
1726 streamheader = gst_structure_get_value (structure, "streamheader");
1727 if (!kate_streamheader_to_codecdata (streamheader, context)) {
1728 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1729 ("kate stream headers missing or malformed"));
1740 * gst_matroska_mux_request_new_pad:
1741 * @element: #GstMatroskaMux.
1742 * @templ: #GstPadTemplate.
1743 * @pad_name: New pad name.
1745 * Request pad function for sink templates.
1747 * Returns: New #GstPad.
1750 gst_matroska_mux_request_new_pad (GstElement * element,
1751 GstPadTemplate * templ, const gchar * pad_name)
1753 GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
1754 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
1755 GstMatroskaPad *collect_pad;
1756 GstPad *newpad = NULL;
1758 GstPadSetCapsFunction setcapsfunc = NULL;
1759 GstMatroskaTrackContext *context = NULL;
1761 if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
1762 name = g_strdup_printf ("audio_%d", mux->num_a_streams++);
1763 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_audio_pad_setcaps);
1764 context = (GstMatroskaTrackContext *)
1765 g_new0 (GstMatroskaTrackAudioContext, 1);
1766 context->type = GST_MATROSKA_TRACK_TYPE_AUDIO;
1767 context->name = g_strdup ("Audio");
1768 } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
1769 name = g_strdup_printf ("video_%d", mux->num_v_streams++);
1770 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_video_pad_setcaps);
1771 context = (GstMatroskaTrackContext *)
1772 g_new0 (GstMatroskaTrackVideoContext, 1);
1773 context->type = GST_MATROSKA_TRACK_TYPE_VIDEO;
1774 context->name = g_strdup ("Video");
1775 } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%d")) {
1776 name = g_strdup_printf ("subtitle_%d", mux->num_t_streams++);
1777 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_subtitle_pad_setcaps);
1778 context = (GstMatroskaTrackContext *)
1779 g_new0 (GstMatroskaTrackSubtitleContext, 1);
1780 context->type = GST_MATROSKA_TRACK_TYPE_SUBTITLE;
1781 context->name = g_strdup ("Subtitle");
1783 GST_WARNING_OBJECT (mux, "This is not our template!");
1787 newpad = gst_pad_new_from_template (templ, name);
1789 collect_pad = (GstMatroskaPad *)
1790 gst_collect_pads_add_pad_full (mux->collect, newpad,
1791 sizeof (GstMatroskaPad),
1792 (GstCollectDataDestroyNotify) gst_matroska_pad_free);
1794 collect_pad->track = context;
1795 gst_matroska_pad_reset (collect_pad, FALSE);
1797 /* FIXME: hacked way to override/extend the event function of
1798 * GstCollectPads; because it sets its own event function giving the
1799 * element no access to events.
1800 * TODO GstCollectPads should really give its 'users' a clean chance to
1801 * properly handle events that are not meant for collectpads itself.
1802 * Perhaps a callback or so, though rejected (?) in #340060.
1803 * This would allow (clean) transcoding of info from demuxer/streams
1804 * to another muxer */
1805 mux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
1806 gst_pad_set_event_function (newpad,
1807 GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_sink_event));
1809 gst_pad_set_setcaps_function (newpad, setcapsfunc);
1810 gst_pad_set_active (newpad, TRUE);
1811 gst_element_add_pad (element, newpad);
1818 * gst_matroska_mux_release_pad:
1819 * @element: #GstMatroskaMux.
1820 * @pad: Pad to release.
1822 * Release a previously requested pad.
1825 gst_matroska_mux_release_pad (GstElement * element, GstPad * pad)
1827 GstMatroskaMux *mux;
1830 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1832 for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
1833 GstCollectData *cdata = (GstCollectData *) walk->data;
1834 GstMatroskaPad *collect_pad = (GstMatroskaPad *) cdata;
1836 if (cdata->pad == pad) {
1837 GstClockTime min_dur; /* observed minimum duration */
1839 if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) &&
1840 GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) {
1841 min_dur = GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts);
1842 if (collect_pad->duration < min_dur)
1843 collect_pad->duration = min_dur;
1846 if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
1847 mux->duration < collect_pad->duration)
1848 mux->duration = collect_pad->duration;
1854 gst_collect_pads_remove_pad (mux->collect, pad);
1855 if (gst_element_remove_pad (element, pad))
1861 * gst_matroska_mux_track_header:
1862 * @mux: #GstMatroskaMux
1863 * @context: Tack context.
1865 * Write a track header.
1868 gst_matroska_mux_track_header (GstMatroskaMux * mux,
1869 GstMatroskaTrackContext * context)
1871 GstEbmlWrite *ebml = mux->ebml_write;
1874 /* TODO: check if everything necessary is written and check default values */
1876 /* track type goes before the type-specific stuff */
1877 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKNUMBER, context->num);
1878 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKTYPE, context->type);
1880 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKUID,
1881 gst_matroska_mux_create_uid ());
1882 if (context->default_duration) {
1883 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKDEFAULTDURATION,
1884 context->default_duration);
1886 if (context->language) {
1887 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKLANGUAGE,
1891 /* type-specific stuff */
1892 switch (context->type) {
1893 case GST_MATROSKA_TRACK_TYPE_VIDEO:{
1894 GstMatroskaTrackVideoContext *videocontext =
1895 (GstMatroskaTrackVideoContext *) context;
1897 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKVIDEO);
1898 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELWIDTH,
1899 videocontext->pixel_width);
1900 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELHEIGHT,
1901 videocontext->pixel_height);
1902 if (videocontext->display_width && videocontext->display_height) {
1903 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYWIDTH,
1904 videocontext->display_width);
1905 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYHEIGHT,
1906 videocontext->display_height);
1908 if (context->flags & GST_MATROSKA_VIDEOTRACK_INTERLACED)
1909 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOFLAGINTERLACED, 1);
1910 if (videocontext->fourcc) {
1911 guint32 fcc_le = GUINT32_TO_LE (videocontext->fourcc);
1913 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_VIDEOCOLOURSPACE,
1914 (gpointer) & fcc_le, 4);
1916 gst_ebml_write_master_finish (ebml, master);
1921 case GST_MATROSKA_TRACK_TYPE_AUDIO:{
1922 GstMatroskaTrackAudioContext *audiocontext =
1923 (GstMatroskaTrackAudioContext *) context;
1925 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKAUDIO);
1926 if (audiocontext->samplerate != 8000)
1927 gst_ebml_write_float (ebml, GST_MATROSKA_ID_AUDIOSAMPLINGFREQ,
1928 audiocontext->samplerate);
1929 if (audiocontext->channels != 1)
1930 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOCHANNELS,
1931 audiocontext->channels);
1932 if (audiocontext->bitdepth) {
1933 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOBITDEPTH,
1934 audiocontext->bitdepth);
1936 gst_ebml_write_master_finish (ebml, master);
1942 /* doesn't need type-specific data */
1946 gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CODECID, context->codec_id);
1947 if (context->codec_priv)
1948 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECPRIVATE,
1949 context->codec_priv, context->codec_priv_size);
1950 /* FIXME: until we have a nice way of getting the codecname
1951 * out of the caps, I'm not going to enable this. Too much
1952 * (useless, double, boring) work... */
1953 /* TODO: Use value from tags if any */
1954 /*gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CODECNAME,
1955 context->codec_name); */
1956 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKNAME, context->name);
1961 * gst_matroska_mux_start:
1962 * @mux: #GstMatroskaMux
1964 * Start a new matroska file (write headers etc...)
1967 gst_matroska_mux_start (GstMatroskaMux * mux)
1969 GstEbmlWrite *ebml = mux->ebml_write;
1970 guint32 seekhead_id[] = { GST_MATROSKA_ID_SEGMENTINFO,
1971 GST_MATROSKA_ID_TRACKS,
1972 GST_MATROSKA_ID_CUES,
1973 GST_MATROSKA_ID_TAGS,
1976 guint64 master, child;
1980 GstClockTime duration = 0;
1981 guint32 segment_uid[4];
1982 GTimeVal time = { 0, 0 };
1984 /* we start with a EBML header */
1985 gst_ebml_write_header (ebml, "matroska", mux->matroska_version);
1987 /* start a segment */
1989 gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENT);
1990 mux->segment_master = ebml->pos;
1992 /* the rest of the header is cached */
1993 gst_ebml_write_set_cache (ebml, 0x1000);
1995 /* seekhead (table of contents) - we set the positions later */
1996 mux->seekhead_pos = ebml->pos;
1997 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD);
1998 for (i = 0; seekhead_id[i] != 0; i++) {
1999 child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKENTRY);
2000 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, seekhead_id[i]);
2001 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION, -1);
2002 gst_ebml_write_master_finish (ebml, child);
2004 gst_ebml_write_master_finish (ebml, master);
2007 mux->info_pos = ebml->pos;
2008 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENTINFO);
2009 for (i = 0; i < 4; i++) {
2010 segment_uid[i] = g_random_int ();
2012 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_SEGMENTUID,
2013 (guint8 *) segment_uid, 16);
2014 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TIMECODESCALE, mux->time_scale);
2015 mux->duration_pos = ebml->pos;
2017 for (collected = mux->collect->data; collected;
2018 collected = g_slist_next (collected)) {
2019 GstMatroskaPad *collect_pad;
2020 GstFormat format = GST_FORMAT_TIME;
2022 gint64 trackduration;
2024 collect_pad = (GstMatroskaPad *) collected->data;
2025 thepad = collect_pad->collect.pad;
2027 /* Query the total length of the track. */
2028 GST_DEBUG_OBJECT (thepad, "querying peer duration");
2029 if (gst_pad_query_peer_duration (thepad, &format, &trackduration)) {
2030 GST_DEBUG_OBJECT (thepad, "duration: %" GST_TIME_FORMAT,
2031 GST_TIME_ARGS (trackduration));
2032 if (trackduration != GST_CLOCK_TIME_NONE && trackduration > duration) {
2033 duration = (GstClockTime) trackduration;
2037 gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
2038 gst_guint64_to_gdouble (duration) /
2039 gst_guint64_to_gdouble (mux->time_scale));
2041 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_MUXINGAPP,
2042 "GStreamer plugin version " PACKAGE_VERSION);
2043 if (mux->writing_app && mux->writing_app[0]) {
2044 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_WRITINGAPP, mux->writing_app);
2046 g_get_current_time (&time);
2047 gst_ebml_write_date (ebml, GST_MATROSKA_ID_DATEUTC, time.tv_sec);
2048 gst_ebml_write_master_finish (ebml, master);
2051 mux->tracks_pos = ebml->pos;
2052 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKS);
2054 for (collected = mux->collect->data; collected;
2055 collected = g_slist_next (collected)) {
2056 GstMatroskaPad *collect_pad;
2059 collect_pad = (GstMatroskaPad *) collected->data;
2060 thepad = collect_pad->collect.pad;
2062 if (gst_pad_is_linked (thepad) && gst_pad_is_active (thepad) &&
2063 collect_pad->track->codec_id != 0) {
2064 collect_pad->track->num = tracknum++;
2065 child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKENTRY);
2066 gst_matroska_mux_track_header (mux, collect_pad->track);
2067 gst_ebml_write_master_finish (ebml, child);
2070 gst_ebml_write_master_finish (ebml, master);
2072 /* lastly, flush the cache */
2073 gst_ebml_write_flush_cache (ebml);
2077 gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag,
2080 /* TODO: more sensible tag mappings */
2083 gchar *matroska_tagname;
2084 gchar *gstreamer_tagname;
2088 GST_MATROSKA_TAG_ID_TITLE, GST_TAG_TITLE}, {
2089 GST_MATROSKA_TAG_ID_AUTHOR, GST_TAG_ARTIST}, {
2090 GST_MATROSKA_TAG_ID_ALBUM, GST_TAG_ALBUM}, {
2091 GST_MATROSKA_TAG_ID_COMMENTS, GST_TAG_COMMENT}, {
2092 GST_MATROSKA_TAG_ID_BITSPS, GST_TAG_BITRATE}, {
2093 GST_MATROSKA_TAG_ID_BPS, GST_TAG_BITRATE}, {
2094 GST_MATROSKA_TAG_ID_ENCODER, GST_TAG_ENCODER}, {
2095 GST_MATROSKA_TAG_ID_DATE, GST_TAG_DATE}, {
2096 GST_MATROSKA_TAG_ID_ISRC, GST_TAG_ISRC}, {
2097 GST_MATROSKA_TAG_ID_COPYRIGHT, GST_TAG_COPYRIGHT}, {
2098 GST_MATROSKA_TAG_ID_BPM, GST_TAG_BEATS_PER_MINUTE}, {
2099 GST_MATROSKA_TAG_ID_TERMS_OF_USE, GST_TAG_LICENSE}, {
2100 GST_MATROSKA_TAG_ID_COMPOSER, GST_TAG_COMPOSER}, {
2101 GST_MATROSKA_TAG_ID_LEAD_PERFORMER, GST_TAG_PERFORMER}, {
2102 GST_MATROSKA_TAG_ID_GENRE, GST_TAG_GENRE}
2104 GstEbmlWrite *ebml = (GstEbmlWrite *) data;
2106 guint64 simpletag_master;
2108 for (i = 0; i < G_N_ELEMENTS (tag_conv); i++) {
2109 const gchar *tagname_gst = tag_conv[i].gstreamer_tagname;
2110 const gchar *tagname_mkv = tag_conv[i].matroska_tagname;
2112 if (strcmp (tagname_gst, tag) == 0) {
2113 GValue src = { 0, };
2116 if (!gst_tag_list_copy_value (&src, list, tag))
2118 if ((dest = gst_value_serialize (&src))) {
2120 simpletag_master = gst_ebml_write_master_start (ebml,
2121 GST_MATROSKA_ID_SIMPLETAG);
2122 gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_TAGNAME, tagname_mkv);
2123 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TAGSTRING, dest);
2124 gst_ebml_write_master_finish (ebml, simpletag_master);
2127 GST_WARNING ("Can't transform tag '%s' to string", tagname_mkv);
2129 g_value_unset (&src);
2137 * gst_matroska_mux_finish:
2138 * @mux: #GstMatroskaMux
2140 * Finish a new matroska file (write index etc...)
2143 gst_matroska_mux_finish (GstMatroskaMux * mux)
2145 GstEbmlWrite *ebml = mux->ebml_write;
2147 guint64 duration = 0;
2149 const GstTagList *tags;
2151 /* finish last cluster */
2153 gst_ebml_write_master_finish (ebml, mux->cluster);
2157 if (mux->index != NULL) {
2159 guint64 master, pointentry_master, trackpos_master;
2161 mux->cues_pos = ebml->pos;
2162 gst_ebml_write_set_cache (ebml, 12 + 41 * mux->num_indexes);
2163 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CUES);
2165 for (n = 0; n < mux->num_indexes; n++) {
2166 GstMatroskaIndex *idx = &mux->index[n];
2168 pointentry_master = gst_ebml_write_master_start (ebml,
2169 GST_MATROSKA_ID_POINTENTRY);
2170 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETIME,
2171 idx->time / mux->time_scale);
2172 trackpos_master = gst_ebml_write_master_start (ebml,
2173 GST_MATROSKA_ID_CUETRACKPOSITIONS);
2174 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETRACK, idx->track);
2175 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUECLUSTERPOSITION,
2176 idx->pos - mux->segment_master);
2177 gst_ebml_write_master_finish (ebml, trackpos_master);
2178 gst_ebml_write_master_finish (ebml, pointentry_master);
2181 gst_ebml_write_master_finish (ebml, master);
2182 gst_ebml_write_flush_cache (ebml);
2186 tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (mux));
2188 if (tags != NULL && !gst_tag_list_is_empty (tags)) {
2189 guint64 master_tags, master_tag;
2191 GST_DEBUG ("Writing tags");
2193 /* TODO: maybe limit via the TARGETS id by looking at the source pad */
2194 mux->tags_pos = ebml->pos;
2195 master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS);
2196 master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG);
2197 gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml);
2198 gst_ebml_write_master_finish (ebml, master_tag);
2199 gst_ebml_write_master_finish (ebml, master_tags);
2202 /* update seekhead. We know that:
2203 * - a seekhead contains 4 entries.
2204 * - order of entries is as above.
2205 * - a seekhead has a 4-byte header + 8-byte length
2206 * - each entry is 2-byte master, 2-byte ID pointer,
2207 * 2-byte length pointer, all 8/1-byte length, 4-
2208 * byte ID and 8-byte length pointer, where the
2209 * length pointer starts at 20.
2210 * - all entries are local to the segment (so pos - segment_master).
2211 * - so each entry is at 12 + 20 + num * 28. */
2212 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 32,
2213 mux->info_pos - mux->segment_master);
2214 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 60,
2215 mux->tracks_pos - mux->segment_master);
2216 if (mux->index != NULL) {
2217 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88,
2218 mux->cues_pos - mux->segment_master);
2221 guint64 my_pos = ebml->pos;
2223 gst_ebml_write_seek (ebml, mux->seekhead_pos + 68);
2224 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
2225 gst_ebml_write_seek (ebml, my_pos);
2228 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116,
2229 mux->tags_pos - mux->segment_master);
2232 guint64 my_pos = ebml->pos;
2234 gst_ebml_write_seek (ebml, mux->seekhead_pos + 96);
2235 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
2236 gst_ebml_write_seek (ebml, my_pos);
2239 /* update duration */
2240 /* first get the overall duration */
2241 /* a released track may have left a duration in here */
2242 duration = mux->duration;
2243 for (collected = mux->collect->data; collected;
2244 collected = g_slist_next (collected)) {
2245 GstMatroskaPad *collect_pad;
2246 GstClockTime min_duration; /* observed minimum duration */
2248 collect_pad = (GstMatroskaPad *) collected->data;
2250 GST_DEBUG_OBJECT (mux, "Pad %" GST_PTR_FORMAT " start ts %" GST_TIME_FORMAT
2251 " end ts %" GST_TIME_FORMAT, collect_pad,
2252 GST_TIME_ARGS (collect_pad->start_ts),
2253 GST_TIME_ARGS (collect_pad->end_ts));
2255 if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) &&
2256 GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) {
2258 GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts);
2259 if (collect_pad->duration < min_duration)
2260 collect_pad->duration = min_duration;
2261 GST_DEBUG_OBJECT (collect_pad, "final track duration: %" GST_TIME_FORMAT,
2262 GST_TIME_ARGS (collect_pad->duration));
2265 if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
2266 duration < collect_pad->duration)
2267 duration = collect_pad->duration;
2269 if (duration != 0) {
2270 GST_DEBUG_OBJECT (mux, "final total duration: %" GST_TIME_FORMAT,
2271 GST_TIME_ARGS (duration));
2272 pos = mux->ebml_write->pos;
2273 gst_ebml_write_seek (ebml, mux->duration_pos);
2274 gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
2275 gst_guint64_to_gdouble (duration) /
2276 gst_guint64_to_gdouble (mux->time_scale));
2277 gst_ebml_write_seek (ebml, pos);
2280 guint64 my_pos = ebml->pos;
2282 gst_ebml_write_seek (ebml, mux->duration_pos);
2283 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 8);
2284 gst_ebml_write_seek (ebml, my_pos);
2287 /* finish segment - this also writes element length */
2288 gst_ebml_write_master_finish (ebml, mux->segment_pos);
2293 * gst_matroska_mux_best_pad:
2294 * @mux: #GstMatroskaMux
2295 * @popped: True if at least one buffer was popped from #GstCollectPads
2297 * Find a pad with the oldest data
2298 * (data from this pad should be written first).
2300 * Returns: Selected pad.
2302 static GstMatroskaPad *
2303 gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped)
2306 GstMatroskaPad *best = NULL;
2309 for (collected = mux->collect->data; collected;
2310 collected = g_slist_next (collected)) {
2311 GstMatroskaPad *collect_pad;
2313 collect_pad = (GstMatroskaPad *) collected->data;
2314 /* fetch a new buffer if needed */
2315 if (collect_pad->buffer == NULL) {
2316 collect_pad->buffer = gst_collect_pads_pop (mux->collect,
2317 (GstCollectData *) collect_pad);
2319 if (collect_pad->buffer != NULL)
2323 /* if we have a buffer check if it is better then the current best one */
2324 if (collect_pad->buffer != NULL) {
2325 if (best == NULL || !GST_BUFFER_TIMESTAMP_IS_VALID (collect_pad->buffer)
2326 || (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)
2327 && GST_BUFFER_TIMESTAMP (collect_pad->buffer) <
2328 GST_BUFFER_TIMESTAMP (best->buffer))) {
2338 * gst_matroska_mux_buffer_header:
2339 * @track: Track context.
2340 * @relative_timestamp: relative timestamp of the buffer
2341 * @flags: Buffer flags.
2343 * Create a buffer containing buffer header.
2345 * Returns: New buffer.
2348 gst_matroska_mux_create_buffer_header (GstMatroskaTrackContext * track,
2349 gint16 relative_timestamp, int flags)
2353 hdr = gst_buffer_new_and_alloc (4);
2354 /* track num - FIXME: what if num >= 0x80 (unlikely)? */
2355 GST_BUFFER_DATA (hdr)[0] = track->num | 0x80;
2356 /* time relative to clustertime */
2357 GST_WRITE_UINT16_BE (GST_BUFFER_DATA (hdr) + 1, relative_timestamp);
2360 GST_BUFFER_DATA (hdr)[3] = flags;
2365 #define DIRAC_PARSE_CODE_SEQUENCE_HEADER 0x00
2366 #define DIRAC_PARSE_CODE_END_OF_SEQUENCE 0x10
2367 #define DIRAC_PARSE_CODE_IS_PICTURE(x) ((x & 0x08) != 0)
2370 gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux,
2371 GstMatroskaPad * collect_pad, GstBuffer * buf)
2373 GstMatroskaTrackVideoContext *ctx =
2374 (GstMatroskaTrackVideoContext *) collect_pad->track;
2375 const guint8 *data = GST_BUFFER_DATA (buf);
2376 guint size = GST_BUFFER_SIZE (buf);
2378 guint32 next_parse_offset;
2379 GstBuffer *ret = NULL;
2380 gboolean is_muxing_unit = FALSE;
2382 if (GST_BUFFER_SIZE (buf) < 13) {
2383 gst_buffer_unref (buf);
2387 /* Check if this buffer contains a picture or end-of-sequence packet */
2388 while (size >= 13) {
2389 if (GST_READ_UINT32_BE (data) != 0x42424344 /* 'BBCD' */ ) {
2390 gst_buffer_unref (buf);
2394 parse_code = GST_READ_UINT8 (data + 4);
2395 if (parse_code == DIRAC_PARSE_CODE_SEQUENCE_HEADER) {
2396 if (ctx->dirac_unit) {
2397 gst_buffer_unref (ctx->dirac_unit);
2398 ctx->dirac_unit = NULL;
2400 } else if (DIRAC_PARSE_CODE_IS_PICTURE (parse_code) ||
2401 parse_code == DIRAC_PARSE_CODE_END_OF_SEQUENCE) {
2402 is_muxing_unit = TRUE;
2406 next_parse_offset = GST_READ_UINT32_BE (data + 5);
2408 if (G_UNLIKELY (next_parse_offset == 0))
2411 data += next_parse_offset;
2412 size -= next_parse_offset;
2415 if (ctx->dirac_unit)
2416 ctx->dirac_unit = gst_buffer_join (ctx->dirac_unit, gst_buffer_ref (buf));
2418 ctx->dirac_unit = gst_buffer_ref (buf);
2420 if (is_muxing_unit) {
2421 ret = gst_buffer_make_metadata_writable (ctx->dirac_unit);
2422 ctx->dirac_unit = NULL;
2423 gst_buffer_copy_metadata (ret, buf,
2424 GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS |
2425 GST_BUFFER_COPY_CAPS);
2426 gst_buffer_unref (buf);
2428 gst_buffer_unref (buf);
2436 * gst_matroska_mux_write_data:
2437 * @mux: #GstMatroskaMux
2438 * @collect_pad: #GstMatroskaPad with the data
2440 * Write collected data (called from gst_matroska_mux_collected).
2442 * Returns: Result of the gst_pad_push issued to write the data.
2444 static GstFlowReturn
2445 gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
2447 GstEbmlWrite *ebml = mux->ebml_write;
2448 GstBuffer *buf, *hdr;
2450 gboolean write_duration;
2451 gint16 relative_timestamp;
2452 gint64 relative_timestamp64;
2453 guint64 block_duration;
2454 gboolean is_video_keyframe = FALSE;
2457 buf = collect_pad->buffer;
2458 collect_pad->buffer = NULL;
2460 /* vorbis/theora headers are retrieved from caps and put in CodecPrivate */
2461 if (collect_pad->track->xiph_headers_to_skip > 0) {
2462 GST_LOG_OBJECT (collect_pad->collect.pad, "dropping streamheader buffer");
2463 gst_buffer_unref (buf);
2464 --collect_pad->track->xiph_headers_to_skip;
2468 /* for dirac we have to queue up everything up to a picture unit */
2469 if (collect_pad->track->codec_id != NULL &&
2470 strcmp (collect_pad->track->codec_id,
2471 GST_MATROSKA_CODEC_ID_VIDEO_DIRAC) == 0) {
2472 buf = gst_matroska_mux_handle_dirac_packet (mux, collect_pad, buf);
2477 /* hm, invalid timestamp (due to --to be fixed--- element upstream);
2478 * this would wreak havoc with time stored in matroska file */
2479 /* TODO: maybe calculate a timestamp by using the previous timestamp
2480 * and default duration */
2481 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
2482 GST_WARNING_OBJECT (collect_pad->collect.pad,
2483 "Invalid buffer timestamp; dropping buffer");
2484 gst_buffer_unref (buf);
2488 /* set the timestamp for outgoing buffers */
2489 ebml->timestamp = GST_BUFFER_TIMESTAMP (buf);
2491 if (collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_VIDEO &&
2492 !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
2493 GST_LOG_OBJECT (mux, "have video keyframe, ts=%" GST_TIME_FORMAT,
2494 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
2495 is_video_keyframe = TRUE;
2499 /* start a new cluster every two seconds or at keyframe */
2500 if (mux->cluster_time + GST_SECOND * 2 < GST_BUFFER_TIMESTAMP (buf)
2501 || is_video_keyframe) {
2503 gst_ebml_write_master_finish (ebml, mux->cluster);
2504 mux->cluster_pos = ebml->pos;
2506 gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
2507 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
2508 GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
2509 mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
2514 mux->cluster_pos = ebml->pos;
2515 mux->cluster = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
2516 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
2517 GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
2518 mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
2521 /* update duration of this track */
2522 if (GST_BUFFER_DURATION_IS_VALID (buf))
2523 collect_pad->duration += GST_BUFFER_DURATION (buf);
2525 /* We currently write index entries for all video tracks or for the audio
2526 * track in a single-track audio file. This could be improved by keeping the
2527 * index only for the *first* video track. */
2529 /* TODO: index is useful for every track, should contain the number of
2530 * the block in the cluster which contains the timestamp, should also work
2531 * for files with multiple audio tracks.
2533 if (is_video_keyframe ||
2534 ((collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_AUDIO) &&
2535 (mux->num_streams == 1))) {
2538 if (mux->min_index_interval != 0) {
2539 for (last_idx = mux->num_indexes - 1; last_idx >= 0; last_idx--) {
2540 if (mux->index[last_idx].track == collect_pad->track->num)
2545 if (last_idx < 0 || mux->min_index_interval == 0 ||
2546 (GST_CLOCK_DIFF (mux->index[last_idx].time, GST_BUFFER_TIMESTAMP (buf))
2547 >= mux->min_index_interval)) {
2548 GstMatroskaIndex *idx;
2550 if (mux->num_indexes % 32 == 0) {
2551 mux->index = g_renew (GstMatroskaIndex, mux->index,
2552 mux->num_indexes + 32);
2554 idx = &mux->index[mux->num_indexes++];
2556 idx->pos = mux->cluster_pos;
2557 idx->time = GST_BUFFER_TIMESTAMP (buf);
2558 idx->track = collect_pad->track->num;
2562 /* Check if the duration differs from the default duration. */
2563 write_duration = FALSE;
2564 block_duration = GST_BUFFER_DURATION (buf);
2565 if (GST_BUFFER_DURATION_IS_VALID (buf)) {
2566 if (block_duration != collect_pad->track->default_duration) {
2567 write_duration = TRUE;
2571 /* write the block, for matroska v2 use SimpleBlock if possible
2572 * one slice (*breath*).
2573 * FIXME: Need to do correct lacing! */
2574 relative_timestamp64 = GST_BUFFER_TIMESTAMP (buf) - mux->cluster_time;
2575 if (relative_timestamp64 >= 0) {
2576 /* round the timestamp */
2577 relative_timestamp64 += mux->time_scale / 2;
2579 /* round the timestamp */
2580 relative_timestamp64 -= mux->time_scale / 2;
2582 relative_timestamp = relative_timestamp64 / (gint64) mux->time_scale;
2583 if (mux->matroska_version > 1 && !write_duration) {
2585 GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) ? 0 : 0x80;
2588 gst_matroska_mux_create_buffer_header (collect_pad->track,
2589 relative_timestamp, flags);
2590 gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_SIMPLEBLOCK,
2591 GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
2592 gst_ebml_write_buffer (ebml, hdr);
2593 gst_ebml_write_buffer (ebml, buf);
2595 return gst_ebml_last_write_result (ebml);
2597 blockgroup = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_BLOCKGROUP);
2599 gst_matroska_mux_create_buffer_header (collect_pad->track,
2600 relative_timestamp, 0);
2601 gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_BLOCK,
2602 GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
2603 gst_ebml_write_buffer (ebml, hdr);
2604 gst_ebml_write_buffer (ebml, buf);
2605 if (write_duration) {
2606 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_BLOCKDURATION,
2607 block_duration / mux->time_scale);
2609 gst_ebml_write_master_finish (ebml, blockgroup);
2610 return gst_ebml_last_write_result (ebml);
2616 * gst_matroska_mux_collected:
2617 * @pads: #GstCollectPads
2618 * @uuser_data: #GstMatroskaMux
2620 * Collectpads callback.
2622 * Returns: #GstFlowReturn
2624 static GstFlowReturn
2625 gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data)
2627 GstMatroskaMux *mux = GST_MATROSKA_MUX (user_data);
2628 GstMatroskaPad *best;
2632 GST_DEBUG_OBJECT (mux, "Collected pads");
2634 /* start with a header */
2635 if (mux->state == GST_MATROSKA_MUX_STATE_START) {
2636 if (mux->collect->data == NULL) {
2637 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
2638 ("No input streams configured"));
2639 return GST_FLOW_ERROR;
2641 mux->state = GST_MATROSKA_MUX_STATE_HEADER;
2642 gst_matroska_mux_start (mux);
2643 mux->state = GST_MATROSKA_MUX_STATE_DATA;
2647 /* which stream to write from? */
2648 best = gst_matroska_mux_best_pad (mux, &popped);
2650 /* if there is no best pad, we have reached EOS */
2652 GST_DEBUG_OBJECT (mux, "No best pad finishing...");
2653 gst_matroska_mux_finish (mux);
2654 gst_pad_push_event (mux->srcpad, gst_event_new_eos ());
2655 ret = GST_FLOW_UNEXPECTED;
2658 GST_DEBUG_OBJECT (best->collect.pad, "best pad - buffer ts %"
2659 GST_TIME_FORMAT " dur %" GST_TIME_FORMAT,
2660 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (best->buffer)),
2661 GST_TIME_ARGS (GST_BUFFER_DURATION (best->buffer)));
2663 /* make note of first and last encountered timestamps, so we can calculate
2664 * the actual duration later when we send an updated header on eos */
2665 if (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)) {
2666 GstClockTime start_ts = GST_BUFFER_TIMESTAMP (best->buffer);
2667 GstClockTime end_ts = start_ts;
2669 if (GST_BUFFER_DURATION_IS_VALID (best->buffer))
2670 end_ts += GST_BUFFER_DURATION (best->buffer);
2671 else if (best->track->default_duration)
2672 end_ts += best->track->default_duration;
2674 if (!GST_CLOCK_TIME_IS_VALID (best->end_ts) || end_ts > best->end_ts)
2675 best->end_ts = end_ts;
2677 if (G_UNLIKELY (best->start_ts == GST_CLOCK_TIME_NONE ||
2678 start_ts < best->start_ts))
2679 best->start_ts = start_ts;
2682 /* write one buffer */
2683 ret = gst_matroska_mux_write_data (mux, best);
2684 } while (ret == GST_FLOW_OK && !popped);
2691 * gst_matroska_mux_change_state:
2692 * @element: #GstMatroskaMux
2693 * @transition: State change transition.
2695 * Change the muxer state.
2697 * Returns: #GstStateChangeReturn
2699 static GstStateChangeReturn
2700 gst_matroska_mux_change_state (GstElement * element, GstStateChange transition)
2702 GstStateChangeReturn ret;
2703 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
2705 switch (transition) {
2706 case GST_STATE_CHANGE_NULL_TO_READY:
2708 case GST_STATE_CHANGE_READY_TO_PAUSED:
2709 gst_collect_pads_start (mux->collect);
2711 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
2713 case GST_STATE_CHANGE_PAUSED_TO_READY:
2714 gst_collect_pads_stop (mux->collect);
2720 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2722 switch (transition) {
2723 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
2725 case GST_STATE_CHANGE_PAUSED_TO_READY:
2726 gst_matroska_mux_reset (GST_ELEMENT (mux));
2728 case GST_STATE_CHANGE_READY_TO_NULL:
2738 gst_matroska_mux_set_property (GObject * object,
2739 guint prop_id, const GValue * value, GParamSpec * pspec)
2741 GstMatroskaMux *mux;
2743 g_return_if_fail (GST_IS_MATROSKA_MUX (object));
2744 mux = GST_MATROSKA_MUX (object);
2747 case ARG_WRITING_APP:
2748 if (!g_value_get_string (value)) {
2749 GST_WARNING_OBJECT (mux, "writing-app property can not be NULL");
2752 g_free (mux->writing_app);
2753 mux->writing_app = g_value_dup_string (value);
2755 case ARG_MATROSKA_VERSION:
2756 mux->matroska_version = g_value_get_int (value);
2758 case ARG_MIN_INDEX_INTERVAL:
2759 mux->min_index_interval = g_value_get_int64 (value);
2762 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2768 gst_matroska_mux_get_property (GObject * object,
2769 guint prop_id, GValue * value, GParamSpec * pspec)
2771 GstMatroskaMux *mux;
2773 g_return_if_fail (GST_IS_MATROSKA_MUX (object));
2774 mux = GST_MATROSKA_MUX (object);
2777 case ARG_WRITING_APP:
2778 g_value_set_string (value, mux->writing_app);
2780 case ARG_MATROSKA_VERSION:
2781 g_value_set_int (value, mux->matroska_version);
2783 case ARG_MIN_INDEX_INTERVAL:
2784 g_value_set_int64 (value, mux->min_index_interval);
2787 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2793 gst_matroska_mux_plugin_init (GstPlugin * plugin)
2795 return gst_element_register (plugin, "matroskamux",
2796 GST_RANK_PRIMARY, GST_TYPE_MATROSKA_MUX);