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 /* WAVEFORMATEX is gst_riff_strf_auds + an extra guint16 extension size */
73 #define WAVEFORMATEX_SIZE (2 + sizeof (gst_riff_strf_auds))
75 static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
78 GST_STATIC_CAPS ("video/x-matroska")
81 #define COMMON_VIDEO_CAPS \
82 "width = (int) [ 16, 4096 ], " \
83 "height = (int) [ 16, 4096 ], " \
84 "framerate = (fraction) [ 0, MAX ]"
86 #define COMMON_VIDEO_CAPS_NO_FRAMERATE \
87 "width = (int) [ 16, 4096 ], " \
88 "height = (int) [ 16, 4096 ] "
91 * * require codec data, etc as needed
94 static GstStaticPadTemplate videosink_templ =
95 GST_STATIC_PAD_TEMPLATE ("video_%d",
98 GST_STATIC_CAPS ("video/mpeg, "
99 "mpegversion = (int) { 1, 2, 4 }, "
100 "systemstream = (boolean) false, "
101 COMMON_VIDEO_CAPS "; "
103 COMMON_VIDEO_CAPS "; "
105 COMMON_VIDEO_CAPS "; "
107 COMMON_VIDEO_CAPS "; "
109 COMMON_VIDEO_CAPS "; "
111 COMMON_VIDEO_CAPS "; "
113 COMMON_VIDEO_CAPS "; "
115 COMMON_VIDEO_CAPS "; "
117 COMMON_VIDEO_CAPS_NO_FRAMERATE "; "
120 COMMON_VIDEO_CAPS "; "
121 "video/x-pn-realvideo, "
122 "rmversion = (int) [1, 4], "
123 COMMON_VIDEO_CAPS "; "
125 "format = (fourcc) { YUY2, I420, YV12, UYVY, AYUV }, "
126 COMMON_VIDEO_CAPS "; "
127 "video/x-wmv, " "wmvversion = (int) [ 1, 3 ], " COMMON_VIDEO_CAPS)
130 #define COMMON_AUDIO_CAPS \
131 "channels = (int) [ 1, MAX ], " \
132 "rate = (int) [ 1, MAX ]"
135 * * require codec data, etc as needed
137 static GstStaticPadTemplate audiosink_templ =
138 GST_STATIC_PAD_TEMPLATE ("audio_%d",
141 GST_STATIC_CAPS ("audio/mpeg, "
142 "mpegversion = (int) 1, "
143 "layer = (int) [ 1, 3 ], "
144 "stream-format = (string) { raw }, "
145 COMMON_AUDIO_CAPS "; "
147 "mpegversion = (int) { 2, 4 }, "
148 COMMON_AUDIO_CAPS "; "
150 COMMON_AUDIO_CAPS "; "
152 COMMON_AUDIO_CAPS "; "
154 COMMON_AUDIO_CAPS "; "
156 COMMON_AUDIO_CAPS "; "
160 "signed = (boolean) false, "
161 COMMON_AUDIO_CAPS ";"
165 "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, "
166 "signed = (boolean) true, "
167 COMMON_AUDIO_CAPS ";"
171 "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, "
172 "signed = (boolean) true, "
173 COMMON_AUDIO_CAPS ";"
177 "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, "
178 "signed = (boolean) true, "
179 COMMON_AUDIO_CAPS ";"
180 "audio/x-raw-float, "
181 "width = (int) [ 32, 64 ], "
182 "endianness = (int) LITTLE_ENDIAN, "
183 COMMON_AUDIO_CAPS ";"
185 "width = (int) { 8, 16, 24 }, "
186 "channels = (int) { 1, 2 }, " "rate = (int) [ 8000, 96000 ]; "
187 "audio/x-pn-realaudio, "
188 "raversion = (int) { 1, 2, 8 }, " COMMON_AUDIO_CAPS "; "
189 "audio/x-wma, " "wmaversion = (int) [ 1, 3 ], "
190 "block_align = (int) [ 0, 65535 ], bitrate = (int) [ 0, 524288 ], "
194 static GstStaticPadTemplate subtitlesink_templ =
195 GST_STATIC_PAD_TEMPLATE ("subtitle_%d",
198 GST_STATIC_CAPS_ANY);
200 static GArray *used_uids;
201 G_LOCK_DEFINE_STATIC (used_uids);
203 static void gst_matroska_mux_add_interfaces (GType type);
205 GType gst_matroska_mux_get_type (void);
206 GST_BOILERPLATE_FULL (GstMatroskaMux, gst_matroska_mux, GstElement,
207 GST_TYPE_ELEMENT, gst_matroska_mux_add_interfaces);
209 /* Matroska muxer destructor */
210 static void gst_matroska_mux_finalize (GObject * object);
212 /* Pads collected callback */
214 gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data);
217 static gboolean gst_matroska_mux_handle_src_event (GstPad * pad,
219 static GstPad *gst_matroska_mux_request_new_pad (GstElement * element,
220 GstPadTemplate * templ, const gchar * name);
221 static void gst_matroska_mux_release_pad (GstElement * element, GstPad * pad);
223 /* gst internal change state handler */
224 static GstStateChangeReturn
225 gst_matroska_mux_change_state (GstElement * element, GstStateChange transition);
227 /* gobject bla bla */
228 static void gst_matroska_mux_set_property (GObject * object,
229 guint prop_id, const GValue * value, GParamSpec * pspec);
230 static void gst_matroska_mux_get_property (GObject * object,
231 guint prop_id, GValue * value, GParamSpec * pspec);
234 static void gst_matroska_mux_reset (GstElement * element);
237 static guint64 gst_matroska_mux_create_uid ();
239 static gboolean theora_streamheader_to_codecdata (const GValue * streamheader,
240 GstMatroskaTrackContext * context);
241 static gboolean vorbis_streamheader_to_codecdata (const GValue * streamheader,
242 GstMatroskaTrackContext * context);
243 static gboolean speex_streamheader_to_codecdata (const GValue * streamheader,
244 GstMatroskaTrackContext * context);
245 static gboolean kate_streamheader_to_codecdata (const GValue * streamheader,
246 GstMatroskaTrackContext * context);
247 static gboolean flac_streamheader_to_codecdata (const GValue * streamheader,
248 GstMatroskaTrackContext * context);
251 gst_matroska_mux_add_interfaces (GType type)
253 static const GInterfaceInfo tag_setter_info = { NULL, NULL, NULL };
255 g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
259 gst_matroska_mux_base_init (gpointer g_class)
261 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
263 gst_element_class_add_pad_template (element_class,
264 gst_static_pad_template_get (&videosink_templ));
265 gst_element_class_add_pad_template (element_class,
266 gst_static_pad_template_get (&audiosink_templ));
267 gst_element_class_add_pad_template (element_class,
268 gst_static_pad_template_get (&subtitlesink_templ));
269 gst_element_class_add_pad_template (element_class,
270 gst_static_pad_template_get (&src_templ));
271 gst_element_class_set_details_simple (element_class, "Matroska muxer",
273 "Muxes video/audio/subtitle streams into a matroska stream",
274 "GStreamer maintainers <gstreamer-devel@lists.sourceforge.net>");
276 GST_DEBUG_CATEGORY_INIT (matroskamux_debug, "matroskamux", 0,
281 gst_matroska_mux_class_init (GstMatroskaMuxClass * klass)
283 GObjectClass *gobject_class;
284 GstElementClass *gstelement_class;
286 gobject_class = (GObjectClass *) klass;
287 gstelement_class = (GstElementClass *) klass;
289 gobject_class->finalize = gst_matroska_mux_finalize;
291 gobject_class->get_property = gst_matroska_mux_get_property;
292 gobject_class->set_property = gst_matroska_mux_set_property;
294 g_object_class_install_property (gobject_class, ARG_WRITING_APP,
295 g_param_spec_string ("writing-app", "Writing application.",
296 "The name the application that creates the matroska file.",
297 NULL, G_PARAM_READWRITE));
298 g_object_class_install_property (gobject_class, ARG_MATROSKA_VERSION,
299 g_param_spec_int ("version", "Matroska version",
300 "This parameter determines what matroska features can be used.",
301 1, 2, DEFAULT_MATROSKA_VERSION, G_PARAM_READWRITE));
302 g_object_class_install_property (gobject_class, ARG_MIN_INDEX_INTERVAL,
303 g_param_spec_int64 ("min-index-interval", "Minimum time between index "
304 "entries", "An index entry is created every so many nanoseconds.",
305 0, G_MAXINT64, DEFAULT_MIN_INDEX_INTERVAL, G_PARAM_READWRITE));
307 gstelement_class->change_state =
308 GST_DEBUG_FUNCPTR (gst_matroska_mux_change_state);
309 gstelement_class->request_new_pad =
310 GST_DEBUG_FUNCPTR (gst_matroska_mux_request_new_pad);
311 gstelement_class->release_pad =
312 GST_DEBUG_FUNCPTR (gst_matroska_mux_release_pad);
317 * gst_matroska_mux_init:
318 * @mux: #GstMatroskaMux that should be initialized.
319 * @g_class: Class of the muxer.
321 * Matroska muxer constructor.
324 gst_matroska_mux_init (GstMatroskaMux * mux, GstMatroskaMuxClass * g_class)
326 mux->srcpad = gst_pad_new_from_static_template (&src_templ, "src");
327 gst_pad_set_event_function (mux->srcpad, gst_matroska_mux_handle_src_event);
328 gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad);
330 mux->collect = gst_collect_pads_new ();
331 gst_collect_pads_set_function (mux->collect,
332 (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_matroska_mux_collected),
335 mux->ebml_write = gst_ebml_write_new (mux->srcpad);
337 /* property defaults */
338 mux->matroska_version = DEFAULT_MATROSKA_VERSION;
339 mux->writing_app = g_strdup (DEFAULT_WRITING_APP);
340 mux->min_index_interval = DEFAULT_MIN_INDEX_INTERVAL;
342 /* initialize internal variables */
344 mux->num_streams = 0;
345 mux->num_a_streams = 0;
346 mux->num_t_streams = 0;
347 mux->num_v_streams = 0;
349 /* initialize remaining variables */
350 gst_matroska_mux_reset (GST_ELEMENT (mux));
355 * gst_matroska_mux_finalize:
356 * @object: #GstMatroskaMux that should be finalized.
358 * Finalize matroska muxer.
361 gst_matroska_mux_finalize (GObject * object)
363 GstMatroskaMux *mux = GST_MATROSKA_MUX (object);
365 gst_object_unref (mux->collect);
366 gst_object_unref (mux->ebml_write);
367 if (mux->writing_app)
368 g_free (mux->writing_app);
370 G_OBJECT_CLASS (parent_class)->finalize (object);
375 * gst_matroska_mux_create_uid:
377 * Generate new unused track UID.
379 * Returns: New track UID.
382 gst_matroska_mux_create_uid (void)
389 used_uids = g_array_sized_new (FALSE, FALSE, sizeof (guint64), 10);
394 uid = (((guint64) g_random_int ()) << 32) | g_random_int ();
395 for (i = 0; i < used_uids->len; i++) {
396 if (g_array_index (used_uids, guint64, i) == uid) {
401 g_array_append_val (used_uids, uid);
404 G_UNLOCK (used_uids);
410 * gst_matroska_pad_reset:
411 * @collect_pad: the #GstMatroskaPad
413 * Reset and/or release resources of a matroska collect pad.
416 gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full)
419 GstMatroskaTrackType type = 0;
421 /* free track information */
422 if (collect_pad->track != NULL) {
423 /* retrieve for optional later use */
424 name = collect_pad->track->name;
425 type = collect_pad->track->type;
426 /* extra for video */
427 if (type == GST_MATROSKA_TRACK_TYPE_VIDEO) {
428 GstMatroskaTrackVideoContext *ctx =
429 (GstMatroskaTrackVideoContext *) collect_pad->track;
431 if (ctx->dirac_unit) {
432 gst_buffer_unref (ctx->dirac_unit);
433 ctx->dirac_unit = NULL;
436 g_free (collect_pad->track->codec_id);
437 g_free (collect_pad->track->codec_name);
439 g_free (collect_pad->track->name);
440 g_free (collect_pad->track->language);
441 g_free (collect_pad->track->codec_priv);
442 g_free (collect_pad->track);
443 collect_pad->track = NULL;
446 /* free cached buffer */
447 if (collect_pad->buffer != NULL) {
448 gst_buffer_unref (collect_pad->buffer);
449 collect_pad->buffer = NULL;
452 if (!full && type != 0) {
453 GstMatroskaTrackContext *context;
455 /* create a fresh context */
457 case GST_MATROSKA_TRACK_TYPE_VIDEO:
458 context = (GstMatroskaTrackContext *)
459 g_new0 (GstMatroskaTrackVideoContext, 1);
461 case GST_MATROSKA_TRACK_TYPE_AUDIO:
462 context = (GstMatroskaTrackContext *)
463 g_new0 (GstMatroskaTrackAudioContext, 1);
465 case GST_MATROSKA_TRACK_TYPE_SUBTITLE:
466 context = (GstMatroskaTrackContext *)
467 g_new0 (GstMatroskaTrackSubtitleContext, 1);
470 g_assert_not_reached ();
474 context->type = type;
475 context->name = name;
476 /* TODO: check default values for the context */
477 context->flags = GST_MATROSKA_TRACK_ENABLED | GST_MATROSKA_TRACK_DEFAULT;
478 collect_pad->track = context;
479 collect_pad->buffer = NULL;
480 collect_pad->duration = 0;
481 collect_pad->start_ts = GST_CLOCK_TIME_NONE;
482 collect_pad->end_ts = GST_CLOCK_TIME_NONE;
487 * gst_matroska_pad_free:
488 * @collect_pad: the #GstMatroskaPad
490 * Release resources of a matroska collect pad.
493 gst_matroska_pad_free (GstMatroskaPad * collect_pad)
495 gst_matroska_pad_reset (collect_pad, TRUE);
500 * gst_matroska_mux_reset:
501 * @element: #GstMatroskaMux that should be reseted.
503 * Reset matroska muxer back to initial state.
506 gst_matroska_mux_reset (GstElement * element)
508 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
511 /* reset EBML write */
512 gst_ebml_write_reset (mux->ebml_write);
515 mux->state = GST_MATROSKA_MUX_STATE_START;
517 /* clean up existing streams */
519 for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
520 GstMatroskaPad *collect_pad;
522 collect_pad = (GstMatroskaPad *) walk->data;
524 /* reset collect pad to pristine state */
525 gst_matroska_pad_reset (collect_pad, FALSE);
529 mux->num_indexes = 0;
534 mux->time_scale = GST_MSECOND;
539 mux->cluster_time = 0;
540 mux->cluster_pos = 0;
543 gst_tag_setter_reset_tags (GST_TAG_SETTER (mux));
547 * gst_matroska_mux_handle_src_event:
548 * @pad: Pad which received the event.
549 * @event: Received event.
551 * handle events - copied from oggmux without understanding
553 * Returns: #TRUE on success.
556 gst_matroska_mux_handle_src_event (GstPad * pad, GstEvent * event)
560 type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;
564 /* disable seeking for now */
570 return gst_pad_event_default (pad, event);
574 * gst_matroska_mux_handle_sink_event:
575 * @pad: Pad which received the event.
576 * @event: Received event.
578 * handle events - informational ones like tags
580 * Returns: #TRUE on success.
583 gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event)
585 GstMatroskaTrackContext *context;
586 GstMatroskaPad *collect_pad;
591 mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad));
593 /* FIXME: aren't we either leaking events here or doing a wrong unref? */
594 switch (GST_EVENT_TYPE (event)) {
598 GST_DEBUG_OBJECT (mux, "received tag event");
599 gst_event_parse_tag (event, &list);
601 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
602 g_assert (collect_pad);
603 context = collect_pad->track;
606 /* Matroska wants ISO 639-2B code, taglist most likely contains 639-1 */
607 if (gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &lang)) {
608 const gchar *lang_code;
610 lang_code = gst_tag_get_language_code_iso_639_2B (lang);
612 GST_INFO_OBJECT (pad, "Setting language to '%s'", lang_code);
613 context->language = g_strdup (lang_code);
615 GST_WARNING_OBJECT (pad, "Did not get language code for '%s'", lang);
620 gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), list,
621 gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (mux)));
624 case GST_EVENT_NEWSEGMENT:
625 /* We don't support NEWSEGMENT events */
627 gst_event_unref (event);
633 /* now GstCollectPads can take care of the rest, e.g. EOS */
635 ret = mux->collect_event (pad, event);
636 gst_object_unref (mux);
643 * gst_matroska_mux_video_pad_setcaps:
644 * @pad: Pad which got the caps.
647 * Setcaps function for video sink pad.
649 * Returns: #TRUE on success.
652 gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
654 GstMatroskaTrackContext *context = NULL;
655 GstMatroskaTrackVideoContext *videocontext;
657 GstMatroskaPad *collect_pad;
658 GstStructure *structure;
659 const gchar *mimetype;
660 const GValue *value = NULL;
661 const GstBuffer *codec_buf = NULL;
662 gint width, height, pixel_width, pixel_height;
664 gboolean interlaced = FALSE;
666 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
669 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
670 g_assert (collect_pad);
671 context = collect_pad->track;
673 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO);
674 videocontext = (GstMatroskaTrackVideoContext *) context;
676 /* gst -> matroska ID'ing */
677 structure = gst_caps_get_structure (caps, 0);
679 mimetype = gst_structure_get_name (structure);
681 if (gst_structure_get_boolean (structure, "interlaced", &interlaced)
683 context->flags |= GST_MATROSKA_VIDEOTRACK_INTERLACED;
685 if (!strcmp (mimetype, "video/x-theora")) {
686 /* we'll extract the details later from the theora identification header */
690 /* get general properties */
691 /* spec says it is mandatory */
692 if (!gst_structure_get_int (structure, "width", &width) ||
693 !gst_structure_get_int (structure, "height", &height))
696 videocontext->pixel_width = width;
697 videocontext->pixel_height = height;
698 if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d)
700 context->default_duration =
701 gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n);
702 GST_LOG_OBJECT (pad, "default duration = %" GST_TIME_FORMAT,
703 GST_TIME_ARGS (context->default_duration));
705 context->default_duration = 0;
707 if (gst_structure_get_fraction (structure, "pixel-aspect-ratio",
708 &pixel_width, &pixel_height)) {
709 if (pixel_width > pixel_height) {
710 videocontext->display_width = width * pixel_width / pixel_height;
711 videocontext->display_height = height;
712 } else if (pixel_width < pixel_height) {
713 videocontext->display_width = width;
714 videocontext->display_height = height * pixel_height / pixel_width;
716 videocontext->display_width = 0;
717 videocontext->display_height = 0;
720 videocontext->display_width = 0;
721 videocontext->display_height = 0;
726 videocontext->asr_mode = GST_MATROSKA_ASPECT_RATIO_MODE_FREE;
727 videocontext->fourcc = 0;
729 /* TODO: - check if we handle all codecs by the spec, i.e. codec private
730 * data and other settings
734 /* extract codec_data, may turn out needed */
735 value = gst_structure_get_value (structure, "codec_data");
737 codec_buf = gst_value_get_buffer (value);
740 if (!strcmp (mimetype, "video/x-raw-yuv")) {
741 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED);
742 gst_structure_get_fourcc (structure, "format", &videocontext->fourcc);
743 } else if (!strcmp (mimetype, "image/jpeg")) {
744 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
745 } else if (!strcmp (mimetype, "video/x-xvid") /* MS/VfW compatibility cases */
746 ||!strcmp (mimetype, "video/x-huffyuv")
747 || !strcmp (mimetype, "video/x-divx")
748 || !strcmp (mimetype, "video/x-dv")
749 || !strcmp (mimetype, "video/x-h263")
750 || !strcmp (mimetype, "video/x-msmpeg")
751 || !strcmp (mimetype, "video/x-wmv")) {
752 gst_riff_strf_vids *bih;
753 gint size = sizeof (gst_riff_strf_vids);
756 if (!strcmp (mimetype, "video/x-xvid"))
757 fourcc = GST_MAKE_FOURCC ('X', 'V', 'I', 'D');
758 else if (!strcmp (mimetype, "video/x-huffyuv"))
759 fourcc = GST_MAKE_FOURCC ('H', 'F', 'Y', 'U');
760 else if (!strcmp (mimetype, "video/x-dv"))
761 fourcc = GST_MAKE_FOURCC ('D', 'V', 'S', 'D');
762 else if (!strcmp (mimetype, "video/x-h263"))
763 fourcc = GST_MAKE_FOURCC ('H', '2', '6', '3');
764 else if (!strcmp (mimetype, "video/x-divx")) {
767 gst_structure_get_int (structure, "divxversion", &divxversion);
768 switch (divxversion) {
770 fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', '3');
773 fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', 'X');
776 fourcc = GST_MAKE_FOURCC ('D', 'X', '5', '0');
779 } else if (!strcmp (mimetype, "video/x-msmpeg")) {
782 gst_structure_get_int (structure, "msmpegversion", &msmpegversion);
783 switch (msmpegversion) {
785 fourcc = GST_MAKE_FOURCC ('M', 'P', 'G', '4');
788 fourcc = GST_MAKE_FOURCC ('M', 'P', '4', '2');
794 } else if (!strcmp (mimetype, "video/x-wmv")) {
797 if (gst_structure_get_fourcc (structure, "format", &format)) {
799 } else if (gst_structure_get_int (structure, "wmvversion", &wmvversion)) {
800 if (wmvversion == 2) {
801 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '2');
802 } else if (wmvversion == 1) {
803 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '1');
804 } else if (wmvversion == 3) {
805 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '3');
813 bih = g_new0 (gst_riff_strf_vids, 1);
814 GST_WRITE_UINT32_LE (&bih->size, size);
815 GST_WRITE_UINT32_LE (&bih->width, videocontext->pixel_width);
816 GST_WRITE_UINT32_LE (&bih->height, videocontext->pixel_height);
817 GST_WRITE_UINT32_LE (&bih->compression, fourcc);
818 GST_WRITE_UINT16_LE (&bih->planes, (guint16) 1);
819 GST_WRITE_UINT16_LE (&bih->bit_cnt, (guint16) 24);
820 GST_WRITE_UINT32_LE (&bih->image_size, videocontext->pixel_width *
821 videocontext->pixel_height * 3);
823 /* process codec private/initialization data, if any */
825 size += GST_BUFFER_SIZE (codec_buf);
826 bih = g_realloc (bih, size);
827 GST_WRITE_UINT32_LE (&bih->size, size);
828 memcpy ((guint8 *) bih + sizeof (gst_riff_strf_vids),
829 GST_BUFFER_DATA (codec_buf), GST_BUFFER_SIZE (codec_buf));
832 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
833 context->codec_priv = (gpointer) bih;
834 context->codec_priv_size = size;
835 } else if (!strcmp (mimetype, "video/x-h264")) {
836 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC);
838 if (context->codec_priv != NULL) {
839 g_free (context->codec_priv);
840 context->codec_priv = NULL;
841 context->codec_priv_size = 0;
844 /* Create avcC header */
845 if (codec_buf != NULL) {
846 context->codec_priv_size = GST_BUFFER_SIZE (codec_buf);
847 context->codec_priv = g_malloc0 (context->codec_priv_size);
848 memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
849 context->codec_priv_size);
851 } else if (!strcmp (mimetype, "video/x-theora")) {
852 const GValue *streamheader;
854 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_THEORA);
856 if (context->codec_priv != NULL) {
857 g_free (context->codec_priv);
858 context->codec_priv = NULL;
859 context->codec_priv_size = 0;
862 streamheader = gst_structure_get_value (structure, "streamheader");
863 if (!theora_streamheader_to_codecdata (streamheader, context)) {
864 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
865 ("theora stream headers missing or malformed"));
868 } else if (!strcmp (mimetype, "video/x-dirac")) {
869 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_DIRAC);
870 } else if (!strcmp (mimetype, "video/mpeg")) {
873 gst_structure_get_int (structure, "mpegversion", &mpegversion);
874 switch (mpegversion) {
876 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG1);
879 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG2);
882 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
888 /* global headers may be in codec data */
889 if (codec_buf != NULL) {
890 context->codec_priv_size = GST_BUFFER_SIZE (codec_buf);
891 context->codec_priv = g_malloc0 (context->codec_priv_size);
892 memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
893 context->codec_priv_size);
895 } else if (!strcmp (mimetype, "video/x-msmpeg")) {
897 /* can only make it here if preceding case verified it was version 3 */
898 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3);
899 } else if (!strcmp (mimetype, "video/x-pn-realvideo")) {
901 const GValue *mdpr_data;
903 gst_structure_get_int (structure, "rmversion", &rmversion);
906 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1);
909 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2);
912 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3);
915 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4);
921 mdpr_data = gst_structure_get_value (structure, "mdpr_data");
922 if (mdpr_data != NULL) {
923 guint8 *priv_data = NULL;
924 guint priv_data_size = 0;
926 GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data);
928 priv_data_size = GST_BUFFER_SIZE (codec_data_buf);
929 priv_data = g_malloc0 (priv_data_size);
931 memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size);
933 context->codec_priv = priv_data;
934 context->codec_priv_size = priv_data_size;
943 GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT,
944 GST_PAD_NAME (pad), caps);
949 /* N > 0 to expect a particular number of headers, negative if the
950 number of headers is variable */
952 xiphN_streamheader_to_codecdata (const GValue * streamheader,
953 GstMatroskaTrackContext * context, GstBuffer ** p_buf0, int N)
955 GstBuffer **buf = NULL;
958 guint bufi, i, offset, priv_data_size;
960 if (streamheader == NULL)
961 goto no_stream_headers;
963 if (G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY)
966 bufarr = g_value_peek_pointer (streamheader);
967 if (bufarr->len <= 0 || bufarr->len > 255) /* at least one header, and count stored in a byte */
969 if (N > 0 && bufarr->len != N)
972 context->xiph_headers_to_skip = bufarr->len;
974 buf = (GstBuffer **) g_malloc0 (sizeof (GstBuffer *) * bufarr->len);
975 for (i = 0; i < bufarr->len; i++) {
976 GValue *bufval = &g_array_index (bufarr, GValue, i);
978 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
980 goto wrong_content_type;
983 buf[i] = g_value_peek_pointer (bufval);
987 if (bufarr->len > 0) {
988 for (i = 0; i < bufarr->len - 1; i++) {
989 priv_data_size += GST_BUFFER_SIZE (buf[i]) / 0xff + 1;
993 for (i = 0; i < bufarr->len; ++i) {
994 priv_data_size += GST_BUFFER_SIZE (buf[i]);
997 priv_data = g_malloc0 (priv_data_size);
999 priv_data[0] = bufarr->len - 1;
1002 if (bufarr->len > 0) {
1003 for (bufi = 0; bufi < bufarr->len - 1; bufi++) {
1004 for (i = 0; i < GST_BUFFER_SIZE (buf[bufi]) / 0xff; ++i) {
1005 priv_data[offset++] = 0xff;
1007 priv_data[offset++] = GST_BUFFER_SIZE (buf[bufi]) % 0xff;
1011 for (i = 0; i < bufarr->len; ++i) {
1012 memcpy (priv_data + offset, GST_BUFFER_DATA (buf[i]),
1013 GST_BUFFER_SIZE (buf[i]));
1014 offset += GST_BUFFER_SIZE (buf[i]);
1017 context->codec_priv = priv_data;
1018 context->codec_priv_size = priv_data_size;
1021 *p_buf0 = gst_buffer_ref (buf[0]);
1030 GST_WARNING ("required streamheaders missing in sink caps!");
1035 GST_WARNING ("streamheaders are not a GST_TYPE_ARRAY, but a %s",
1036 G_VALUE_TYPE_NAME (streamheader));
1041 GST_WARNING ("got %u streamheaders, not %d as expected", bufarr->len, N);
1046 GST_WARNING ("streamheaders array does not contain GstBuffers");
1052 vorbis_streamheader_to_codecdata (const GValue * streamheader,
1053 GstMatroskaTrackContext * context)
1055 GstBuffer *buf0 = NULL;
1057 if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3))
1060 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 4) {
1061 GST_WARNING ("First vorbis header too small, ignoring");
1063 if (memcmp (GST_BUFFER_DATA (buf0) + 1, "vorbis", 6) == 0) {
1064 GstMatroskaTrackAudioContext *audiocontext;
1067 hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 4;
1068 audiocontext = (GstMatroskaTrackAudioContext *) context;
1069 audiocontext->channels = GST_READ_UINT8 (hdr);
1070 audiocontext->samplerate = GST_READ_UINT32_LE (hdr + 1);
1075 gst_buffer_unref (buf0);
1081 theora_streamheader_to_codecdata (const GValue * streamheader,
1082 GstMatroskaTrackContext * context)
1084 GstBuffer *buf0 = NULL;
1086 if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3))
1089 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 26) {
1090 GST_WARNING ("First theora header too small, ignoring");
1091 } else if (memcmp (GST_BUFFER_DATA (buf0), "\200theora\003\002", 9) != 0) {
1092 GST_WARNING ("First header not a theora identification header, ignoring");
1094 GstMatroskaTrackVideoContext *videocontext;
1095 guint fps_num, fps_denom, par_num, par_denom;
1098 hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 3 + 2 + 2;
1100 videocontext = (GstMatroskaTrackVideoContext *) context;
1101 videocontext->pixel_width = GST_READ_UINT32_BE (hdr) >> 8;
1102 videocontext->pixel_height = GST_READ_UINT32_BE (hdr + 3) >> 8;
1103 hdr += 3 + 3 + 1 + 1;
1104 fps_num = GST_READ_UINT32_BE (hdr);
1105 fps_denom = GST_READ_UINT32_BE (hdr + 4);
1106 context->default_duration = gst_util_uint64_scale_int (GST_SECOND,
1107 fps_denom, fps_num);
1109 par_num = GST_READ_UINT32_BE (hdr) >> 8;
1110 par_denom = GST_READ_UINT32_BE (hdr + 3) >> 8;
1111 if (par_num > 0 && par_num > 0) {
1112 if (par_num > par_denom) {
1113 videocontext->display_width =
1114 videocontext->pixel_width * par_num / par_denom;
1115 videocontext->display_height = videocontext->pixel_height;
1116 } else if (par_num < par_denom) {
1117 videocontext->display_width = videocontext->pixel_width;
1118 videocontext->display_height =
1119 videocontext->pixel_height * par_denom / par_num;
1121 videocontext->display_width = 0;
1122 videocontext->display_height = 0;
1125 videocontext->display_width = 0;
1126 videocontext->display_height = 0;
1132 gst_buffer_unref (buf0);
1138 kate_streamheader_to_codecdata (const GValue * streamheader,
1139 GstMatroskaTrackContext * context)
1141 GstBuffer *buf0 = NULL;
1143 if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, -1))
1146 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 64) { /* Kate ID header is 64 bytes */
1147 GST_WARNING ("First kate header too small, ignoring");
1148 } else if (memcmp (GST_BUFFER_DATA (buf0), "\200kate\0\0\0", 8) != 0) {
1149 GST_WARNING ("First header not a kate identification header, ignoring");
1153 gst_buffer_unref (buf0);
1159 flac_streamheader_to_codecdata (const GValue * streamheader,
1160 GstMatroskaTrackContext * context)
1167 if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) {
1168 GST_WARNING ("No or invalid streamheader field in the caps");
1172 bufarr = g_value_peek_pointer (streamheader);
1173 if (bufarr->len < 2) {
1174 GST_WARNING ("Too few headers in streamheader field");
1178 context->xiph_headers_to_skip = bufarr->len + 1;
1180 bufval = &g_array_index (bufarr, GValue, 0);
1181 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1182 GST_WARNING ("streamheaders array does not contain GstBuffers");
1186 buffer = g_value_peek_pointer (bufval);
1188 /* Need at least OggFLAC mapping header, fLaC marker and STREAMINFO block */
1189 if (GST_BUFFER_SIZE (buffer) < 9 + 4 + 4 + 34
1190 || memcmp (GST_BUFFER_DATA (buffer) + 1, "FLAC", 4) != 0
1191 || memcmp (GST_BUFFER_DATA (buffer) + 9, "fLaC", 4) != 0) {
1192 GST_WARNING ("Invalid streamheader for FLAC");
1196 context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer) - 9);
1197 context->codec_priv_size = GST_BUFFER_SIZE (buffer) - 9;
1198 memcpy (context->codec_priv, GST_BUFFER_DATA (buffer) + 9,
1199 GST_BUFFER_SIZE (buffer) - 9);
1201 for (i = 1; i < bufarr->len; i++) {
1202 bufval = &g_array_index (bufarr, GValue, i);
1204 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1205 g_free (context->codec_priv);
1206 context->codec_priv = NULL;
1207 context->codec_priv_size = 0;
1208 GST_WARNING ("streamheaders array does not contain GstBuffers");
1212 buffer = g_value_peek_pointer (bufval);
1214 context->codec_priv =
1215 g_realloc (context->codec_priv,
1216 context->codec_priv_size + GST_BUFFER_SIZE (buffer));
1217 memcpy ((guint8 *) context->codec_priv + context->codec_priv_size,
1218 GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
1219 context->codec_priv_size =
1220 context->codec_priv_size + GST_BUFFER_SIZE (buffer);
1227 speex_streamheader_to_codecdata (const GValue * streamheader,
1228 GstMatroskaTrackContext * context)
1234 if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) {
1235 GST_WARNING ("No or invalid streamheader field in the caps");
1239 bufarr = g_value_peek_pointer (streamheader);
1240 if (bufarr->len != 2) {
1241 GST_WARNING ("Too few headers in streamheader field");
1245 context->xiph_headers_to_skip = bufarr->len + 1;
1247 bufval = &g_array_index (bufarr, GValue, 0);
1248 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1249 GST_WARNING ("streamheaders array does not contain GstBuffers");
1253 buffer = g_value_peek_pointer (bufval);
1255 if (GST_BUFFER_SIZE (buffer) < 80
1256 || memcmp (GST_BUFFER_DATA (buffer), "Speex ", 8) != 0) {
1257 GST_WARNING ("Invalid streamheader for Speex");
1261 context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer));
1262 context->codec_priv_size = GST_BUFFER_SIZE (buffer);
1263 memcpy (context->codec_priv, GST_BUFFER_DATA (buffer),
1264 GST_BUFFER_SIZE (buffer));
1266 bufval = &g_array_index (bufarr, GValue, 1);
1268 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1269 g_free (context->codec_priv);
1270 context->codec_priv = NULL;
1271 context->codec_priv_size = 0;
1272 GST_WARNING ("streamheaders array does not contain GstBuffers");
1276 buffer = g_value_peek_pointer (bufval);
1278 context->codec_priv =
1279 g_realloc (context->codec_priv,
1280 context->codec_priv_size + GST_BUFFER_SIZE (buffer));
1281 memcpy ((guint8 *) context->codec_priv + context->codec_priv_size,
1282 GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
1283 context->codec_priv_size =
1284 context->codec_priv_size + GST_BUFFER_SIZE (buffer);
1289 static const gchar *
1290 aac_codec_data_to_codec_id (const GstBuffer * buf)
1292 const gchar *result;
1295 /* default to MAIN */
1298 if (GST_BUFFER_SIZE (buf) >= 2) {
1299 profile = GST_READ_UINT8 (GST_BUFFER_DATA (buf));
1317 GST_WARNING ("unknown AAC profile, defaulting to MAIN");
1326 * gst_matroska_mux_audio_pad_setcaps:
1327 * @pad: Pad which got the caps.
1330 * Setcaps function for audio sink pad.
1332 * Returns: #TRUE on success.
1335 gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
1337 GstMatroskaTrackContext *context = NULL;
1338 GstMatroskaTrackAudioContext *audiocontext;
1339 GstMatroskaMux *mux;
1340 GstMatroskaPad *collect_pad;
1341 const gchar *mimetype;
1342 gint samplerate = 0, channels = 0;
1343 GstStructure *structure;
1344 const GValue *codec_data = NULL;
1345 const GstBuffer *buf = NULL;
1346 const gchar *stream_format = NULL;
1348 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1351 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
1352 g_assert (collect_pad);
1353 context = collect_pad->track;
1355 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_AUDIO);
1356 audiocontext = (GstMatroskaTrackAudioContext *) context;
1358 structure = gst_caps_get_structure (caps, 0);
1359 mimetype = gst_structure_get_name (structure);
1362 gst_structure_get_int (structure, "rate", &samplerate);
1363 gst_structure_get_int (structure, "channels", &channels);
1365 audiocontext->samplerate = samplerate;
1366 audiocontext->channels = channels;
1367 audiocontext->bitdepth = 0;
1368 context->default_duration = 0;
1370 codec_data = gst_structure_get_value (structure, "codec_data");
1372 buf = gst_value_get_buffer (codec_data);
1374 /* TODO: - check if we handle all codecs by the spec, i.e. codec private
1375 * data and other settings
1379 if (!strcmp (mimetype, "audio/mpeg")) {
1380 gint mpegversion = 0;
1382 gst_structure_get_int (structure, "mpegversion", &mpegversion);
1383 switch (mpegversion) {
1389 gst_structure_get_int (structure, "layer", &layer);
1391 if (!gst_structure_get_int (structure, "mpegaudioversion", &version)) {
1392 GST_WARNING_OBJECT (mux,
1393 "Unable to determine MPEG audio version, assuming 1");
1399 else if (layer == 2)
1401 else if (version == 2)
1406 context->default_duration =
1407 gst_util_uint64_scale (GST_SECOND, spf, audiocontext->samplerate);
1411 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1);
1414 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2);
1417 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3);
1426 stream_format = gst_structure_get_string (structure, "stream-format");
1427 /* check this is raw aac */
1428 if (stream_format) {
1429 if (strcmp (stream_format, "raw") != 0) {
1430 GST_WARNING_OBJECT (mux, "AAC stream-format must be 'raw', not %s",
1434 GST_WARNING_OBJECT (mux, "AAC stream-format not specified, "
1439 if (mpegversion == 2)
1441 g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG2 "%s",
1442 aac_codec_data_to_codec_id (buf));
1443 else if (mpegversion == 4)
1445 g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4 "%s",
1446 aac_codec_data_to_codec_id (buf));
1448 g_assert_not_reached ();
1450 GST_DEBUG_OBJECT (mux, "no AAC codec_data; not packetized");
1457 } else if (!strcmp (mimetype, "audio/x-raw-int")) {
1459 gint endianness = G_LITTLE_ENDIAN;
1460 gboolean signedness = TRUE;
1462 if (!gst_structure_get_int (structure, "width", &width) ||
1463 !gst_structure_get_int (structure, "depth", &depth) ||
1464 !gst_structure_get_boolean (structure, "signed", &signedness)) {
1465 GST_DEBUG_OBJECT (mux, "broken caps, width/depth/signed field missing");
1470 !gst_structure_get_int (structure, "endianness", &endianness)) {
1471 GST_DEBUG_OBJECT (mux, "broken caps, no endianness specified");
1475 if (width != depth) {
1476 GST_DEBUG_OBJECT (mux, "width must be same as depth!");
1480 /* FIXME: where is this spec'ed out? (tpm) */
1481 if ((width == 8 && signedness) || (width >= 16 && !signedness)) {
1482 GST_DEBUG_OBJECT (mux, "8-bit PCM must be unsigned, 16-bit PCM signed");
1486 audiocontext->bitdepth = depth;
1487 if (endianness == G_BIG_ENDIAN)
1488 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE);
1490 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE);
1492 } else if (!strcmp (mimetype, "audio/x-raw-float")) {
1495 if (!gst_structure_get_int (structure, "width", &width)) {
1496 GST_DEBUG_OBJECT (mux, "broken caps, width field missing");
1500 audiocontext->bitdepth = width;
1501 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT);
1503 } else if (!strcmp (mimetype, "audio/x-vorbis")) {
1504 const GValue *streamheader;
1506 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_VORBIS);
1508 if (context->codec_priv != NULL) {
1509 g_free (context->codec_priv);
1510 context->codec_priv = NULL;
1511 context->codec_priv_size = 0;
1514 streamheader = gst_structure_get_value (structure, "streamheader");
1515 if (!vorbis_streamheader_to_codecdata (streamheader, context)) {
1516 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1517 ("vorbis stream headers missing or malformed"));
1520 } else if (!strcmp (mimetype, "audio/x-flac")) {
1521 const GValue *streamheader;
1523 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_FLAC);
1524 if (context->codec_priv != NULL) {
1525 g_free (context->codec_priv);
1526 context->codec_priv = NULL;
1527 context->codec_priv_size = 0;
1530 streamheader = gst_structure_get_value (structure, "streamheader");
1531 if (!flac_streamheader_to_codecdata (streamheader, context)) {
1532 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1533 ("flac stream headers missing or malformed"));
1536 } else if (!strcmp (mimetype, "audio/x-speex")) {
1537 const GValue *streamheader;
1539 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_SPEEX);
1540 if (context->codec_priv != NULL) {
1541 g_free (context->codec_priv);
1542 context->codec_priv = NULL;
1543 context->codec_priv_size = 0;
1546 streamheader = gst_structure_get_value (structure, "streamheader");
1547 if (!speex_streamheader_to_codecdata (streamheader, context)) {
1548 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1549 ("speex stream headers missing or malformed"));
1552 } else if (!strcmp (mimetype, "audio/x-ac3")) {
1553 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3);
1554 } else if (!strcmp (mimetype, "audio/x-tta")) {
1557 /* TTA frame duration */
1558 context->default_duration = 1.04489795918367346939 * GST_SECOND;
1560 gst_structure_get_int (structure, "width", &width);
1561 audiocontext->bitdepth = width;
1562 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_TTA);
1564 } else if (!strcmp (mimetype, "audio/x-pn-realaudio")) {
1566 const GValue *mdpr_data;
1568 gst_structure_get_int (structure, "raversion", &raversion);
1569 switch (raversion) {
1571 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4);
1574 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_28_8);
1577 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK);
1583 mdpr_data = gst_structure_get_value (structure, "mdpr_data");
1584 if (mdpr_data != NULL) {
1585 guint8 *priv_data = NULL;
1586 guint priv_data_size = 0;
1588 GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data);
1590 priv_data_size = GST_BUFFER_SIZE (codec_data_buf);
1591 priv_data = g_malloc0 (priv_data_size);
1593 memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size);
1595 context->codec_priv = priv_data;
1596 context->codec_priv_size = priv_data_size;
1599 } else if (!strcmp (mimetype, "audio/x-wma")) {
1601 guint codec_priv_size;
1608 if (!gst_structure_get_int (structure, "wmaversion", &wmaversion)
1609 || !gst_structure_get_int (structure, "block_align", &block_align)
1610 || !gst_structure_get_int (structure, "bitrate", &bitrate)
1611 || samplerate == 0 || channels == 0) {
1612 GST_WARNING_OBJECT (mux, "Missing wmaversion/block_align/bitrate/"
1613 "channels/rate on WMA caps");
1617 switch (wmaversion) {
1619 format = GST_RIFF_WAVE_FORMAT_WMAV1;
1622 format = GST_RIFF_WAVE_FORMAT_WMAV2;
1625 format = GST_RIFF_WAVE_FORMAT_WMAV3;
1628 GST_WARNING_OBJECT (mux, "Unexpected WMA version: %d", wmaversion);
1632 if (gst_structure_get_int (structure, "depth", &depth))
1633 audiocontext->bitdepth = depth;
1635 codec_priv_size = WAVEFORMATEX_SIZE;
1637 codec_priv_size += GST_BUFFER_SIZE (buf);
1639 /* serialize waveformatex structure */
1640 codec_priv = g_malloc0 (codec_priv_size);
1641 GST_WRITE_UINT16_LE (codec_priv, format);
1642 GST_WRITE_UINT16_LE (codec_priv + 2, channels);
1643 GST_WRITE_UINT32_LE (codec_priv + 4, samplerate);
1644 GST_WRITE_UINT32_LE (codec_priv + 8, bitrate / 8);
1645 GST_WRITE_UINT16_LE (codec_priv + 12, block_align);
1646 GST_WRITE_UINT16_LE (codec_priv + 14, 0);
1648 GST_WRITE_UINT16_LE (codec_priv + 16, GST_BUFFER_SIZE (buf));
1650 GST_WRITE_UINT16_LE (codec_priv + 16, 0);
1652 /* process codec private/initialization data, if any */
1654 memcpy ((guint8 *) codec_priv + WAVEFORMATEX_SIZE,
1655 GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
1658 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_ACM);
1659 context->codec_priv = (gpointer) codec_priv;
1660 context->codec_priv_size = codec_priv_size;
1668 GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT,
1669 GST_PAD_NAME (pad), caps);
1676 * gst_matroska_mux_subtitle_pad_setcaps:
1677 * @pad: Pad which got the caps.
1680 * Setcaps function for subtitle sink pad.
1682 * Returns: #TRUE on success.
1685 gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps)
1688 * Consider this as boilerplate code for now. There is
1689 * no single subtitle creation element in GStreamer,
1690 * neither do I know how subtitling works at all. */
1692 /* There is now (at least) one such alement (kateenc), and I'm going
1693 to handle it here and claim it works when it can be piped back
1694 through GStreamer and VLC */
1696 GstMatroskaTrackContext *context = NULL;
1697 GstMatroskaTrackSubtitleContext *scontext;
1698 GstMatroskaMux *mux;
1699 GstMatroskaPad *collect_pad;
1700 const gchar *mimetype;
1701 GstStructure *structure;
1703 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1706 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
1707 g_assert (collect_pad);
1708 context = collect_pad->track;
1710 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_SUBTITLE);
1711 scontext = (GstMatroskaTrackSubtitleContext *) context;
1713 structure = gst_caps_get_structure (caps, 0);
1714 mimetype = gst_structure_get_name (structure);
1717 scontext->check_utf8 = 1;
1718 scontext->invalid_utf8 = 0;
1719 context->default_duration = 0;
1721 /* TODO: - other format than Kate */
1723 if (!strcmp (mimetype, "subtitle/x-kate")) {
1724 const GValue *streamheader;
1726 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_SUBTITLE_KATE);
1728 if (context->codec_priv != NULL) {
1729 g_free (context->codec_priv);
1730 context->codec_priv = NULL;
1731 context->codec_priv_size = 0;
1734 streamheader = gst_structure_get_value (structure, "streamheader");
1735 if (!kate_streamheader_to_codecdata (streamheader, context)) {
1736 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1737 ("kate stream headers missing or malformed"));
1748 * gst_matroska_mux_request_new_pad:
1749 * @element: #GstMatroskaMux.
1750 * @templ: #GstPadTemplate.
1751 * @pad_name: New pad name.
1753 * Request pad function for sink templates.
1755 * Returns: New #GstPad.
1758 gst_matroska_mux_request_new_pad (GstElement * element,
1759 GstPadTemplate * templ, const gchar * pad_name)
1761 GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
1762 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
1763 GstMatroskaPad *collect_pad;
1764 GstPad *newpad = NULL;
1766 GstPadSetCapsFunction setcapsfunc = NULL;
1767 GstMatroskaTrackContext *context = NULL;
1769 if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
1770 name = g_strdup_printf ("audio_%d", mux->num_a_streams++);
1771 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_audio_pad_setcaps);
1772 context = (GstMatroskaTrackContext *)
1773 g_new0 (GstMatroskaTrackAudioContext, 1);
1774 context->type = GST_MATROSKA_TRACK_TYPE_AUDIO;
1775 context->name = g_strdup ("Audio");
1776 } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
1777 name = g_strdup_printf ("video_%d", mux->num_v_streams++);
1778 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_video_pad_setcaps);
1779 context = (GstMatroskaTrackContext *)
1780 g_new0 (GstMatroskaTrackVideoContext, 1);
1781 context->type = GST_MATROSKA_TRACK_TYPE_VIDEO;
1782 context->name = g_strdup ("Video");
1783 } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%d")) {
1784 name = g_strdup_printf ("subtitle_%d", mux->num_t_streams++);
1785 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_subtitle_pad_setcaps);
1786 context = (GstMatroskaTrackContext *)
1787 g_new0 (GstMatroskaTrackSubtitleContext, 1);
1788 context->type = GST_MATROSKA_TRACK_TYPE_SUBTITLE;
1789 context->name = g_strdup ("Subtitle");
1791 GST_WARNING_OBJECT (mux, "This is not our template!");
1795 newpad = gst_pad_new_from_template (templ, name);
1797 collect_pad = (GstMatroskaPad *)
1798 gst_collect_pads_add_pad_full (mux->collect, newpad,
1799 sizeof (GstMatroskaPad),
1800 (GstCollectDataDestroyNotify) gst_matroska_pad_free);
1802 collect_pad->track = context;
1803 gst_matroska_pad_reset (collect_pad, FALSE);
1805 /* FIXME: hacked way to override/extend the event function of
1806 * GstCollectPads; because it sets its own event function giving the
1807 * element no access to events.
1808 * TODO GstCollectPads should really give its 'users' a clean chance to
1809 * properly handle events that are not meant for collectpads itself.
1810 * Perhaps a callback or so, though rejected (?) in #340060.
1811 * This would allow (clean) transcoding of info from demuxer/streams
1812 * to another muxer */
1813 mux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
1814 gst_pad_set_event_function (newpad,
1815 GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_sink_event));
1817 gst_pad_set_setcaps_function (newpad, setcapsfunc);
1818 gst_pad_set_active (newpad, TRUE);
1819 gst_element_add_pad (element, newpad);
1826 * gst_matroska_mux_release_pad:
1827 * @element: #GstMatroskaMux.
1828 * @pad: Pad to release.
1830 * Release a previously requested pad.
1833 gst_matroska_mux_release_pad (GstElement * element, GstPad * pad)
1835 GstMatroskaMux *mux;
1838 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1840 for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
1841 GstCollectData *cdata = (GstCollectData *) walk->data;
1842 GstMatroskaPad *collect_pad = (GstMatroskaPad *) cdata;
1844 if (cdata->pad == pad) {
1845 GstClockTime min_dur; /* observed minimum duration */
1847 if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) &&
1848 GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) {
1849 min_dur = GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts);
1850 if (collect_pad->duration < min_dur)
1851 collect_pad->duration = min_dur;
1854 if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
1855 mux->duration < collect_pad->duration)
1856 mux->duration = collect_pad->duration;
1862 gst_collect_pads_remove_pad (mux->collect, pad);
1863 if (gst_element_remove_pad (element, pad))
1869 * gst_matroska_mux_track_header:
1870 * @mux: #GstMatroskaMux
1871 * @context: Tack context.
1873 * Write a track header.
1876 gst_matroska_mux_track_header (GstMatroskaMux * mux,
1877 GstMatroskaTrackContext * context)
1879 GstEbmlWrite *ebml = mux->ebml_write;
1882 /* TODO: check if everything necessary is written and check default values */
1884 /* track type goes before the type-specific stuff */
1885 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKNUMBER, context->num);
1886 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKTYPE, context->type);
1888 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKUID,
1889 gst_matroska_mux_create_uid ());
1890 if (context->default_duration) {
1891 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKDEFAULTDURATION,
1892 context->default_duration);
1894 if (context->language) {
1895 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKLANGUAGE,
1899 /* type-specific stuff */
1900 switch (context->type) {
1901 case GST_MATROSKA_TRACK_TYPE_VIDEO:{
1902 GstMatroskaTrackVideoContext *videocontext =
1903 (GstMatroskaTrackVideoContext *) context;
1905 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKVIDEO);
1906 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELWIDTH,
1907 videocontext->pixel_width);
1908 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELHEIGHT,
1909 videocontext->pixel_height);
1910 if (videocontext->display_width && videocontext->display_height) {
1911 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYWIDTH,
1912 videocontext->display_width);
1913 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYHEIGHT,
1914 videocontext->display_height);
1916 if (context->flags & GST_MATROSKA_VIDEOTRACK_INTERLACED)
1917 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOFLAGINTERLACED, 1);
1918 if (videocontext->fourcc) {
1919 guint32 fcc_le = GUINT32_TO_LE (videocontext->fourcc);
1921 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_VIDEOCOLOURSPACE,
1922 (gpointer) & fcc_le, 4);
1924 gst_ebml_write_master_finish (ebml, master);
1929 case GST_MATROSKA_TRACK_TYPE_AUDIO:{
1930 GstMatroskaTrackAudioContext *audiocontext =
1931 (GstMatroskaTrackAudioContext *) context;
1933 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKAUDIO);
1934 if (audiocontext->samplerate != 8000)
1935 gst_ebml_write_float (ebml, GST_MATROSKA_ID_AUDIOSAMPLINGFREQ,
1936 audiocontext->samplerate);
1937 if (audiocontext->channels != 1)
1938 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOCHANNELS,
1939 audiocontext->channels);
1940 if (audiocontext->bitdepth) {
1941 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOBITDEPTH,
1942 audiocontext->bitdepth);
1944 gst_ebml_write_master_finish (ebml, master);
1950 /* doesn't need type-specific data */
1954 gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CODECID, context->codec_id);
1955 if (context->codec_priv)
1956 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECPRIVATE,
1957 context->codec_priv, context->codec_priv_size);
1958 /* FIXME: until we have a nice way of getting the codecname
1959 * out of the caps, I'm not going to enable this. Too much
1960 * (useless, double, boring) work... */
1961 /* TODO: Use value from tags if any */
1962 /*gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CODECNAME,
1963 context->codec_name); */
1964 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKNAME, context->name);
1969 * gst_matroska_mux_start:
1970 * @mux: #GstMatroskaMux
1972 * Start a new matroska file (write headers etc...)
1975 gst_matroska_mux_start (GstMatroskaMux * mux)
1977 GstEbmlWrite *ebml = mux->ebml_write;
1978 guint32 seekhead_id[] = { GST_MATROSKA_ID_SEGMENTINFO,
1979 GST_MATROSKA_ID_TRACKS,
1980 GST_MATROSKA_ID_CUES,
1981 GST_MATROSKA_ID_TAGS,
1984 guint64 master, child;
1988 GstClockTime duration = 0;
1989 guint32 segment_uid[4];
1990 GTimeVal time = { 0, 0 };
1992 /* we start with a EBML header */
1993 gst_ebml_write_header (ebml, "matroska", mux->matroska_version);
1995 /* start a segment */
1997 gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENT);
1998 mux->segment_master = ebml->pos;
2000 /* the rest of the header is cached */
2001 gst_ebml_write_set_cache (ebml, 0x1000);
2003 /* seekhead (table of contents) - we set the positions later */
2004 mux->seekhead_pos = ebml->pos;
2005 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD);
2006 for (i = 0; seekhead_id[i] != 0; i++) {
2007 child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKENTRY);
2008 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, seekhead_id[i]);
2009 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION, -1);
2010 gst_ebml_write_master_finish (ebml, child);
2012 gst_ebml_write_master_finish (ebml, master);
2015 mux->info_pos = ebml->pos;
2016 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENTINFO);
2017 for (i = 0; i < 4; i++) {
2018 segment_uid[i] = g_random_int ();
2020 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_SEGMENTUID,
2021 (guint8 *) segment_uid, 16);
2022 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TIMECODESCALE, mux->time_scale);
2023 mux->duration_pos = ebml->pos;
2025 for (collected = mux->collect->data; collected;
2026 collected = g_slist_next (collected)) {
2027 GstMatroskaPad *collect_pad;
2028 GstFormat format = GST_FORMAT_TIME;
2030 gint64 trackduration;
2032 collect_pad = (GstMatroskaPad *) collected->data;
2033 thepad = collect_pad->collect.pad;
2035 /* Query the total length of the track. */
2036 GST_DEBUG_OBJECT (thepad, "querying peer duration");
2037 if (gst_pad_query_peer_duration (thepad, &format, &trackduration)) {
2038 GST_DEBUG_OBJECT (thepad, "duration: %" GST_TIME_FORMAT,
2039 GST_TIME_ARGS (trackduration));
2040 if (trackduration != GST_CLOCK_TIME_NONE && trackduration > duration) {
2041 duration = (GstClockTime) trackduration;
2045 gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
2046 gst_guint64_to_gdouble (duration) /
2047 gst_guint64_to_gdouble (mux->time_scale));
2049 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_MUXINGAPP,
2050 "GStreamer plugin version " PACKAGE_VERSION);
2051 if (mux->writing_app && mux->writing_app[0]) {
2052 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_WRITINGAPP, mux->writing_app);
2054 g_get_current_time (&time);
2055 gst_ebml_write_date (ebml, GST_MATROSKA_ID_DATEUTC, time.tv_sec);
2056 gst_ebml_write_master_finish (ebml, master);
2059 mux->tracks_pos = ebml->pos;
2060 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKS);
2062 for (collected = mux->collect->data; collected;
2063 collected = g_slist_next (collected)) {
2064 GstMatroskaPad *collect_pad;
2067 collect_pad = (GstMatroskaPad *) collected->data;
2068 thepad = collect_pad->collect.pad;
2070 if (gst_pad_is_linked (thepad) && gst_pad_is_active (thepad) &&
2071 collect_pad->track->codec_id != 0) {
2072 collect_pad->track->num = tracknum++;
2073 child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKENTRY);
2074 gst_matroska_mux_track_header (mux, collect_pad->track);
2075 gst_ebml_write_master_finish (ebml, child);
2078 gst_ebml_write_master_finish (ebml, master);
2080 /* lastly, flush the cache */
2081 gst_ebml_write_flush_cache (ebml);
2085 gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag,
2088 /* TODO: more sensible tag mappings */
2091 const gchar *matroska_tagname;
2092 const gchar *gstreamer_tagname;
2096 GST_MATROSKA_TAG_ID_TITLE, GST_TAG_TITLE}, {
2097 GST_MATROSKA_TAG_ID_AUTHOR, GST_TAG_ARTIST}, {
2098 GST_MATROSKA_TAG_ID_ALBUM, GST_TAG_ALBUM}, {
2099 GST_MATROSKA_TAG_ID_COMMENTS, GST_TAG_COMMENT}, {
2100 GST_MATROSKA_TAG_ID_BITSPS, GST_TAG_BITRATE}, {
2101 GST_MATROSKA_TAG_ID_BPS, GST_TAG_BITRATE}, {
2102 GST_MATROSKA_TAG_ID_ENCODER, GST_TAG_ENCODER}, {
2103 GST_MATROSKA_TAG_ID_DATE, GST_TAG_DATE}, {
2104 GST_MATROSKA_TAG_ID_ISRC, GST_TAG_ISRC}, {
2105 GST_MATROSKA_TAG_ID_COPYRIGHT, GST_TAG_COPYRIGHT}, {
2106 GST_MATROSKA_TAG_ID_BPM, GST_TAG_BEATS_PER_MINUTE}, {
2107 GST_MATROSKA_TAG_ID_TERMS_OF_USE, GST_TAG_LICENSE}, {
2108 GST_MATROSKA_TAG_ID_COMPOSER, GST_TAG_COMPOSER}, {
2109 GST_MATROSKA_TAG_ID_LEAD_PERFORMER, GST_TAG_PERFORMER}, {
2110 GST_MATROSKA_TAG_ID_GENRE, GST_TAG_GENRE}
2112 GstEbmlWrite *ebml = (GstEbmlWrite *) data;
2114 guint64 simpletag_master;
2116 for (i = 0; i < G_N_ELEMENTS (tag_conv); i++) {
2117 const gchar *tagname_gst = tag_conv[i].gstreamer_tagname;
2118 const gchar *tagname_mkv = tag_conv[i].matroska_tagname;
2120 if (strcmp (tagname_gst, tag) == 0) {
2121 GValue src = { 0, };
2124 if (!gst_tag_list_copy_value (&src, list, tag))
2126 if ((dest = gst_value_serialize (&src))) {
2128 simpletag_master = gst_ebml_write_master_start (ebml,
2129 GST_MATROSKA_ID_SIMPLETAG);
2130 gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_TAGNAME, tagname_mkv);
2131 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TAGSTRING, dest);
2132 gst_ebml_write_master_finish (ebml, simpletag_master);
2135 GST_WARNING ("Can't transform tag '%s' to string", tagname_mkv);
2137 g_value_unset (&src);
2145 * gst_matroska_mux_finish:
2146 * @mux: #GstMatroskaMux
2148 * Finish a new matroska file (write index etc...)
2151 gst_matroska_mux_finish (GstMatroskaMux * mux)
2153 GstEbmlWrite *ebml = mux->ebml_write;
2155 guint64 duration = 0;
2157 const GstTagList *tags;
2159 /* finish last cluster */
2161 gst_ebml_write_master_finish (ebml, mux->cluster);
2165 if (mux->index != NULL) {
2167 guint64 master, pointentry_master, trackpos_master;
2169 mux->cues_pos = ebml->pos;
2170 gst_ebml_write_set_cache (ebml, 12 + 41 * mux->num_indexes);
2171 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CUES);
2173 for (n = 0; n < mux->num_indexes; n++) {
2174 GstMatroskaIndex *idx = &mux->index[n];
2176 pointentry_master = gst_ebml_write_master_start (ebml,
2177 GST_MATROSKA_ID_POINTENTRY);
2178 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETIME,
2179 idx->time / mux->time_scale);
2180 trackpos_master = gst_ebml_write_master_start (ebml,
2181 GST_MATROSKA_ID_CUETRACKPOSITIONS);
2182 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETRACK, idx->track);
2183 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUECLUSTERPOSITION,
2184 idx->pos - mux->segment_master);
2185 gst_ebml_write_master_finish (ebml, trackpos_master);
2186 gst_ebml_write_master_finish (ebml, pointentry_master);
2189 gst_ebml_write_master_finish (ebml, master);
2190 gst_ebml_write_flush_cache (ebml);
2194 tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (mux));
2196 if (tags != NULL && !gst_tag_list_is_empty (tags)) {
2197 guint64 master_tags, master_tag;
2199 GST_DEBUG ("Writing tags");
2201 /* TODO: maybe limit via the TARGETS id by looking at the source pad */
2202 mux->tags_pos = ebml->pos;
2203 master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS);
2204 master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG);
2205 gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml);
2206 gst_ebml_write_master_finish (ebml, master_tag);
2207 gst_ebml_write_master_finish (ebml, master_tags);
2210 /* update seekhead. We know that:
2211 * - a seekhead contains 4 entries.
2212 * - order of entries is as above.
2213 * - a seekhead has a 4-byte header + 8-byte length
2214 * - each entry is 2-byte master, 2-byte ID pointer,
2215 * 2-byte length pointer, all 8/1-byte length, 4-
2216 * byte ID and 8-byte length pointer, where the
2217 * length pointer starts at 20.
2218 * - all entries are local to the segment (so pos - segment_master).
2219 * - so each entry is at 12 + 20 + num * 28. */
2220 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 32,
2221 mux->info_pos - mux->segment_master);
2222 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 60,
2223 mux->tracks_pos - mux->segment_master);
2224 if (mux->index != NULL) {
2225 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88,
2226 mux->cues_pos - mux->segment_master);
2229 guint64 my_pos = ebml->pos;
2231 gst_ebml_write_seek (ebml, mux->seekhead_pos + 68);
2232 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
2233 gst_ebml_write_seek (ebml, my_pos);
2236 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116,
2237 mux->tags_pos - mux->segment_master);
2240 guint64 my_pos = ebml->pos;
2242 gst_ebml_write_seek (ebml, mux->seekhead_pos + 96);
2243 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
2244 gst_ebml_write_seek (ebml, my_pos);
2247 /* update duration */
2248 /* first get the overall duration */
2249 /* a released track may have left a duration in here */
2250 duration = mux->duration;
2251 for (collected = mux->collect->data; collected;
2252 collected = g_slist_next (collected)) {
2253 GstMatroskaPad *collect_pad;
2254 GstClockTime min_duration; /* observed minimum duration */
2256 collect_pad = (GstMatroskaPad *) collected->data;
2258 GST_DEBUG_OBJECT (mux, "Pad %" GST_PTR_FORMAT " start ts %" GST_TIME_FORMAT
2259 " end ts %" GST_TIME_FORMAT, collect_pad,
2260 GST_TIME_ARGS (collect_pad->start_ts),
2261 GST_TIME_ARGS (collect_pad->end_ts));
2263 if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) &&
2264 GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) {
2266 GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts);
2267 if (collect_pad->duration < min_duration)
2268 collect_pad->duration = min_duration;
2269 GST_DEBUG_OBJECT (collect_pad, "final track duration: %" GST_TIME_FORMAT,
2270 GST_TIME_ARGS (collect_pad->duration));
2273 if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
2274 duration < collect_pad->duration)
2275 duration = collect_pad->duration;
2277 if (duration != 0) {
2278 GST_DEBUG_OBJECT (mux, "final total duration: %" GST_TIME_FORMAT,
2279 GST_TIME_ARGS (duration));
2280 pos = mux->ebml_write->pos;
2281 gst_ebml_write_seek (ebml, mux->duration_pos);
2282 gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
2283 gst_guint64_to_gdouble (duration) /
2284 gst_guint64_to_gdouble (mux->time_scale));
2285 gst_ebml_write_seek (ebml, pos);
2288 guint64 my_pos = ebml->pos;
2290 gst_ebml_write_seek (ebml, mux->duration_pos);
2291 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 8);
2292 gst_ebml_write_seek (ebml, my_pos);
2295 /* finish segment - this also writes element length */
2296 gst_ebml_write_master_finish (ebml, mux->segment_pos);
2301 * gst_matroska_mux_best_pad:
2302 * @mux: #GstMatroskaMux
2303 * @popped: True if at least one buffer was popped from #GstCollectPads
2305 * Find a pad with the oldest data
2306 * (data from this pad should be written first).
2308 * Returns: Selected pad.
2310 static GstMatroskaPad *
2311 gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped)
2314 GstMatroskaPad *best = NULL;
2317 for (collected = mux->collect->data; collected;
2318 collected = g_slist_next (collected)) {
2319 GstMatroskaPad *collect_pad;
2321 collect_pad = (GstMatroskaPad *) collected->data;
2322 /* fetch a new buffer if needed */
2323 if (collect_pad->buffer == NULL) {
2324 collect_pad->buffer = gst_collect_pads_pop (mux->collect,
2325 (GstCollectData *) collect_pad);
2327 if (collect_pad->buffer != NULL)
2331 /* if we have a buffer check if it is better then the current best one */
2332 if (collect_pad->buffer != NULL) {
2333 if (best == NULL || !GST_BUFFER_TIMESTAMP_IS_VALID (collect_pad->buffer)
2334 || (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)
2335 && GST_BUFFER_TIMESTAMP (collect_pad->buffer) <
2336 GST_BUFFER_TIMESTAMP (best->buffer))) {
2346 * gst_matroska_mux_buffer_header:
2347 * @track: Track context.
2348 * @relative_timestamp: relative timestamp of the buffer
2349 * @flags: Buffer flags.
2351 * Create a buffer containing buffer header.
2353 * Returns: New buffer.
2356 gst_matroska_mux_create_buffer_header (GstMatroskaTrackContext * track,
2357 gint16 relative_timestamp, int flags)
2361 hdr = gst_buffer_new_and_alloc (4);
2362 /* track num - FIXME: what if num >= 0x80 (unlikely)? */
2363 GST_BUFFER_DATA (hdr)[0] = track->num | 0x80;
2364 /* time relative to clustertime */
2365 GST_WRITE_UINT16_BE (GST_BUFFER_DATA (hdr) + 1, relative_timestamp);
2368 GST_BUFFER_DATA (hdr)[3] = flags;
2373 #define DIRAC_PARSE_CODE_SEQUENCE_HEADER 0x00
2374 #define DIRAC_PARSE_CODE_END_OF_SEQUENCE 0x10
2375 #define DIRAC_PARSE_CODE_IS_PICTURE(x) ((x & 0x08) != 0)
2378 gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux,
2379 GstMatroskaPad * collect_pad, GstBuffer * buf)
2381 GstMatroskaTrackVideoContext *ctx =
2382 (GstMatroskaTrackVideoContext *) collect_pad->track;
2383 const guint8 *data = GST_BUFFER_DATA (buf);
2384 guint size = GST_BUFFER_SIZE (buf);
2386 guint32 next_parse_offset;
2387 GstBuffer *ret = NULL;
2388 gboolean is_muxing_unit = FALSE;
2390 if (GST_BUFFER_SIZE (buf) < 13) {
2391 gst_buffer_unref (buf);
2395 /* Check if this buffer contains a picture or end-of-sequence packet */
2396 while (size >= 13) {
2397 if (GST_READ_UINT32_BE (data) != 0x42424344 /* 'BBCD' */ ) {
2398 gst_buffer_unref (buf);
2402 parse_code = GST_READ_UINT8 (data + 4);
2403 if (parse_code == DIRAC_PARSE_CODE_SEQUENCE_HEADER) {
2404 if (ctx->dirac_unit) {
2405 gst_buffer_unref (ctx->dirac_unit);
2406 ctx->dirac_unit = NULL;
2408 } else if (DIRAC_PARSE_CODE_IS_PICTURE (parse_code) ||
2409 parse_code == DIRAC_PARSE_CODE_END_OF_SEQUENCE) {
2410 is_muxing_unit = TRUE;
2414 next_parse_offset = GST_READ_UINT32_BE (data + 5);
2416 if (G_UNLIKELY (next_parse_offset == 0))
2419 data += next_parse_offset;
2420 size -= next_parse_offset;
2423 if (ctx->dirac_unit)
2424 ctx->dirac_unit = gst_buffer_join (ctx->dirac_unit, gst_buffer_ref (buf));
2426 ctx->dirac_unit = gst_buffer_ref (buf);
2428 if (is_muxing_unit) {
2429 ret = gst_buffer_make_metadata_writable (ctx->dirac_unit);
2430 ctx->dirac_unit = NULL;
2431 gst_buffer_copy_metadata (ret, buf,
2432 GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS |
2433 GST_BUFFER_COPY_CAPS);
2434 gst_buffer_unref (buf);
2436 gst_buffer_unref (buf);
2444 * gst_matroska_mux_write_data:
2445 * @mux: #GstMatroskaMux
2446 * @collect_pad: #GstMatroskaPad with the data
2448 * Write collected data (called from gst_matroska_mux_collected).
2450 * Returns: Result of the gst_pad_push issued to write the data.
2452 static GstFlowReturn
2453 gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
2455 GstEbmlWrite *ebml = mux->ebml_write;
2456 GstBuffer *buf, *hdr;
2458 gboolean write_duration;
2459 gint16 relative_timestamp;
2460 gint64 relative_timestamp64;
2461 guint64 block_duration;
2462 gboolean is_video_keyframe = FALSE;
2465 buf = collect_pad->buffer;
2466 collect_pad->buffer = NULL;
2468 /* vorbis/theora headers are retrieved from caps and put in CodecPrivate */
2469 if (collect_pad->track->xiph_headers_to_skip > 0) {
2470 GST_LOG_OBJECT (collect_pad->collect.pad, "dropping streamheader buffer");
2471 gst_buffer_unref (buf);
2472 --collect_pad->track->xiph_headers_to_skip;
2476 /* for dirac we have to queue up everything up to a picture unit */
2477 if (collect_pad->track->codec_id != NULL &&
2478 strcmp (collect_pad->track->codec_id,
2479 GST_MATROSKA_CODEC_ID_VIDEO_DIRAC) == 0) {
2480 buf = gst_matroska_mux_handle_dirac_packet (mux, collect_pad, buf);
2485 /* hm, invalid timestamp (due to --to be fixed--- element upstream);
2486 * this would wreak havoc with time stored in matroska file */
2487 /* TODO: maybe calculate a timestamp by using the previous timestamp
2488 * and default duration */
2489 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
2490 GST_WARNING_OBJECT (collect_pad->collect.pad,
2491 "Invalid buffer timestamp; dropping buffer");
2492 gst_buffer_unref (buf);
2496 /* set the timestamp for outgoing buffers */
2497 ebml->timestamp = GST_BUFFER_TIMESTAMP (buf);
2499 if (collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_VIDEO &&
2500 !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
2501 GST_LOG_OBJECT (mux, "have video keyframe, ts=%" GST_TIME_FORMAT,
2502 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
2503 is_video_keyframe = TRUE;
2507 /* start a new cluster every two seconds or at keyframe */
2508 if (mux->cluster_time + GST_SECOND * 2 < GST_BUFFER_TIMESTAMP (buf)
2509 || is_video_keyframe) {
2511 gst_ebml_write_master_finish (ebml, mux->cluster);
2512 mux->cluster_pos = ebml->pos;
2514 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);
2522 mux->cluster_pos = ebml->pos;
2523 mux->cluster = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
2524 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
2525 GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
2526 mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
2529 /* update duration of this track */
2530 if (GST_BUFFER_DURATION_IS_VALID (buf))
2531 collect_pad->duration += GST_BUFFER_DURATION (buf);
2533 /* We currently write index entries for all video tracks or for the audio
2534 * track in a single-track audio file. This could be improved by keeping the
2535 * index only for the *first* video track. */
2537 /* TODO: index is useful for every track, should contain the number of
2538 * the block in the cluster which contains the timestamp, should also work
2539 * for files with multiple audio tracks.
2541 if (is_video_keyframe ||
2542 ((collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_AUDIO) &&
2543 (mux->num_streams == 1))) {
2546 if (mux->min_index_interval != 0) {
2547 for (last_idx = mux->num_indexes - 1; last_idx >= 0; last_idx--) {
2548 if (mux->index[last_idx].track == collect_pad->track->num)
2553 if (last_idx < 0 || mux->min_index_interval == 0 ||
2554 (GST_CLOCK_DIFF (mux->index[last_idx].time, GST_BUFFER_TIMESTAMP (buf))
2555 >= mux->min_index_interval)) {
2556 GstMatroskaIndex *idx;
2558 if (mux->num_indexes % 32 == 0) {
2559 mux->index = g_renew (GstMatroskaIndex, mux->index,
2560 mux->num_indexes + 32);
2562 idx = &mux->index[mux->num_indexes++];
2564 idx->pos = mux->cluster_pos;
2565 idx->time = GST_BUFFER_TIMESTAMP (buf);
2566 idx->track = collect_pad->track->num;
2570 /* Check if the duration differs from the default duration. */
2571 write_duration = FALSE;
2572 block_duration = GST_BUFFER_DURATION (buf);
2573 if (GST_BUFFER_DURATION_IS_VALID (buf)) {
2574 if (block_duration != collect_pad->track->default_duration) {
2575 write_duration = TRUE;
2579 /* write the block, for matroska v2 use SimpleBlock if possible
2580 * one slice (*breath*).
2581 * FIXME: Need to do correct lacing! */
2582 relative_timestamp64 = GST_BUFFER_TIMESTAMP (buf) - mux->cluster_time;
2583 if (relative_timestamp64 >= 0) {
2584 /* round the timestamp */
2585 relative_timestamp64 += mux->time_scale / 2;
2587 /* round the timestamp */
2588 relative_timestamp64 -= mux->time_scale / 2;
2590 relative_timestamp = relative_timestamp64 / (gint64) mux->time_scale;
2591 if (mux->matroska_version > 1 && !write_duration) {
2593 GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) ? 0 : 0x80;
2596 gst_matroska_mux_create_buffer_header (collect_pad->track,
2597 relative_timestamp, flags);
2598 gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_SIMPLEBLOCK,
2599 GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
2600 gst_ebml_write_buffer (ebml, hdr);
2601 gst_ebml_write_buffer (ebml, buf);
2603 return gst_ebml_last_write_result (ebml);
2605 blockgroup = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_BLOCKGROUP);
2607 gst_matroska_mux_create_buffer_header (collect_pad->track,
2608 relative_timestamp, 0);
2609 gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_BLOCK,
2610 GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
2611 gst_ebml_write_buffer (ebml, hdr);
2612 gst_ebml_write_buffer (ebml, buf);
2613 if (write_duration) {
2614 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_BLOCKDURATION,
2615 block_duration / mux->time_scale);
2617 gst_ebml_write_master_finish (ebml, blockgroup);
2618 return gst_ebml_last_write_result (ebml);
2624 * gst_matroska_mux_collected:
2625 * @pads: #GstCollectPads
2626 * @uuser_data: #GstMatroskaMux
2628 * Collectpads callback.
2630 * Returns: #GstFlowReturn
2632 static GstFlowReturn
2633 gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data)
2635 GstMatroskaMux *mux = GST_MATROSKA_MUX (user_data);
2636 GstMatroskaPad *best;
2640 GST_DEBUG_OBJECT (mux, "Collected pads");
2642 /* start with a header */
2643 if (mux->state == GST_MATROSKA_MUX_STATE_START) {
2644 if (mux->collect->data == NULL) {
2645 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
2646 ("No input streams configured"));
2647 return GST_FLOW_ERROR;
2649 mux->state = GST_MATROSKA_MUX_STATE_HEADER;
2650 gst_matroska_mux_start (mux);
2651 mux->state = GST_MATROSKA_MUX_STATE_DATA;
2655 /* which stream to write from? */
2656 best = gst_matroska_mux_best_pad (mux, &popped);
2658 /* if there is no best pad, we have reached EOS */
2660 GST_DEBUG_OBJECT (mux, "No best pad finishing...");
2661 gst_matroska_mux_finish (mux);
2662 gst_pad_push_event (mux->srcpad, gst_event_new_eos ());
2663 ret = GST_FLOW_UNEXPECTED;
2666 GST_DEBUG_OBJECT (best->collect.pad, "best pad - buffer ts %"
2667 GST_TIME_FORMAT " dur %" GST_TIME_FORMAT,
2668 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (best->buffer)),
2669 GST_TIME_ARGS (GST_BUFFER_DURATION (best->buffer)));
2671 /* make note of first and last encountered timestamps, so we can calculate
2672 * the actual duration later when we send an updated header on eos */
2673 if (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)) {
2674 GstClockTime start_ts = GST_BUFFER_TIMESTAMP (best->buffer);
2675 GstClockTime end_ts = start_ts;
2677 if (GST_BUFFER_DURATION_IS_VALID (best->buffer))
2678 end_ts += GST_BUFFER_DURATION (best->buffer);
2679 else if (best->track->default_duration)
2680 end_ts += best->track->default_duration;
2682 if (!GST_CLOCK_TIME_IS_VALID (best->end_ts) || end_ts > best->end_ts)
2683 best->end_ts = end_ts;
2685 if (G_UNLIKELY (best->start_ts == GST_CLOCK_TIME_NONE ||
2686 start_ts < best->start_ts))
2687 best->start_ts = start_ts;
2690 /* write one buffer */
2691 ret = gst_matroska_mux_write_data (mux, best);
2692 } while (ret == GST_FLOW_OK && !popped);
2699 * gst_matroska_mux_change_state:
2700 * @element: #GstMatroskaMux
2701 * @transition: State change transition.
2703 * Change the muxer state.
2705 * Returns: #GstStateChangeReturn
2707 static GstStateChangeReturn
2708 gst_matroska_mux_change_state (GstElement * element, GstStateChange transition)
2710 GstStateChangeReturn ret;
2711 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
2713 switch (transition) {
2714 case GST_STATE_CHANGE_NULL_TO_READY:
2716 case GST_STATE_CHANGE_READY_TO_PAUSED:
2717 gst_collect_pads_start (mux->collect);
2719 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
2721 case GST_STATE_CHANGE_PAUSED_TO_READY:
2722 gst_collect_pads_stop (mux->collect);
2728 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2730 switch (transition) {
2731 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
2733 case GST_STATE_CHANGE_PAUSED_TO_READY:
2734 gst_matroska_mux_reset (GST_ELEMENT (mux));
2736 case GST_STATE_CHANGE_READY_TO_NULL:
2746 gst_matroska_mux_set_property (GObject * object,
2747 guint prop_id, const GValue * value, GParamSpec * pspec)
2749 GstMatroskaMux *mux;
2751 g_return_if_fail (GST_IS_MATROSKA_MUX (object));
2752 mux = GST_MATROSKA_MUX (object);
2755 case ARG_WRITING_APP:
2756 if (!g_value_get_string (value)) {
2757 GST_WARNING_OBJECT (mux, "writing-app property can not be NULL");
2760 g_free (mux->writing_app);
2761 mux->writing_app = g_value_dup_string (value);
2763 case ARG_MATROSKA_VERSION:
2764 mux->matroska_version = g_value_get_int (value);
2766 case ARG_MIN_INDEX_INTERVAL:
2767 mux->min_index_interval = g_value_get_int64 (value);
2770 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2776 gst_matroska_mux_get_property (GObject * object,
2777 guint prop_id, GValue * value, GParamSpec * pspec)
2779 GstMatroskaMux *mux;
2781 g_return_if_fail (GST_IS_MATROSKA_MUX (object));
2782 mux = GST_MATROSKA_MUX (object);
2785 case ARG_WRITING_APP:
2786 g_value_set_string (value, mux->writing_app);
2788 case ARG_MATROSKA_VERSION:
2789 g_value_set_int (value, mux->matroska_version);
2791 case ARG_MIN_INDEX_INTERVAL:
2792 g_value_set_int64 (value, mux->min_index_interval);
2795 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2801 gst_matroska_mux_plugin_init (GstPlugin * plugin)
2803 return gst_element_register (plugin, "matroskamux",
2804 GST_RANK_PRIMARY, GST_TYPE_MATROSKA_MUX);