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>
53 #include "matroska-mux.h"
54 #include "matroska-ids.h"
56 GST_DEBUG_CATEGORY_STATIC (matroskamux_debug);
57 #define GST_CAT_DEFAULT matroskamux_debug
66 #define DEFAULT_MATROSKA_VERSION 1
67 #define DEFAULT_WRITING_APP "GStreamer Matroska muxer"
69 static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
72 GST_STATIC_CAPS ("video/x-matroska")
75 #define COMMON_VIDEO_CAPS \
76 "width = (int) [ 16, 4096 ], " \
77 "height = (int) [ 16, 4096 ], " \
78 "framerate = (fraction) [ 0, MAX ]"
80 #define COMMON_VIDEO_CAPS_NO_FRAMERATE \
81 "width = (int) [ 16, 4096 ], " \
82 "height = (int) [ 16, 4096 ] "
85 * * require codec data, etc as needed
88 static GstStaticPadTemplate videosink_templ =
89 GST_STATIC_PAD_TEMPLATE ("video_%d",
92 GST_STATIC_CAPS ("video/mpeg, "
93 "mpegversion = (int) { 1, 2, 4 }, "
94 "systemstream = (boolean) false, "
95 COMMON_VIDEO_CAPS "; "
97 COMMON_VIDEO_CAPS "; "
99 COMMON_VIDEO_CAPS "; "
101 COMMON_VIDEO_CAPS "; "
103 COMMON_VIDEO_CAPS "; "
105 COMMON_VIDEO_CAPS "; "
107 COMMON_VIDEO_CAPS "; "
109 COMMON_VIDEO_CAPS "; "
111 COMMON_VIDEO_CAPS_NO_FRAMERATE "; "
114 COMMON_VIDEO_CAPS "; "
115 "video/x-pn-realvideo, "
116 "rmversion = (int) [1, 4], "
117 COMMON_VIDEO_CAPS "; "
119 "format = (fourcc) { YUY2, I420, YV12, UYVY, AYUV }, "
120 COMMON_VIDEO_CAPS "; "
121 "video/x-wmv, " "wmvversion = (int) [ 1, 3 ], " COMMON_VIDEO_CAPS)
124 #define COMMON_AUDIO_CAPS \
125 "channels = (int) [ 1, MAX ], " \
126 "rate = (int) [ 1, MAX ]"
129 * * require codec data, etc as needed
131 static GstStaticPadTemplate audiosink_templ =
132 GST_STATIC_PAD_TEMPLATE ("audio_%d",
135 GST_STATIC_CAPS ("audio/mpeg, "
136 "mpegversion = (int) 1, "
137 "layer = (int) [ 1, 3 ], "
138 COMMON_AUDIO_CAPS "; "
140 "mpegversion = (int) { 2, 4 }, "
141 COMMON_AUDIO_CAPS "; "
143 COMMON_AUDIO_CAPS "; "
145 COMMON_AUDIO_CAPS "; "
147 COMMON_AUDIO_CAPS "; "
149 COMMON_AUDIO_CAPS "; "
153 "signed = (boolean) false, "
154 COMMON_AUDIO_CAPS ";"
158 "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, "
159 "signed = (boolean) true, "
160 COMMON_AUDIO_CAPS ";"
164 "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, "
165 "signed = (boolean) true, "
166 COMMON_AUDIO_CAPS ";"
170 "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, "
171 "signed = (boolean) true, "
172 COMMON_AUDIO_CAPS ";"
173 "audio/x-raw-float, "
174 "width = (int) [ 32, 64 ], "
175 "endianness = (int) LITTLE_ENDIAN, "
176 COMMON_AUDIO_CAPS ";"
178 "width = (int) { 8, 16, 24 }, "
179 "channels = (int) { 1, 2 }, " "rate = (int) [ 8000, 96000 ]; "
180 "audio/x-pn-realaudio, "
181 "raversion = (int) { 1, 2, 8 }, " COMMON_AUDIO_CAPS "; "
182 "audio/x-wma, " "wmaversion = (int) [ 1, 3 ], "
183 "block_align = (int) [ 0, 65535 ], bitrate = (int) [ 0, 524288 ], "
187 static GstStaticPadTemplate subtitlesink_templ =
188 GST_STATIC_PAD_TEMPLATE ("subtitle_%d",
191 GST_STATIC_CAPS_ANY);
193 static GArray *used_uids;
194 G_LOCK_DEFINE_STATIC (used_uids);
196 static void gst_matroska_mux_add_interfaces (GType type);
198 GST_BOILERPLATE_FULL (GstMatroskaMux, gst_matroska_mux, GstElement,
199 GST_TYPE_ELEMENT, gst_matroska_mux_add_interfaces);
201 /* Matroska muxer destructor */
202 static void gst_matroska_mux_finalize (GObject * object);
204 /* Pads collected callback */
206 gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data);
209 static gboolean gst_matroska_mux_handle_src_event (GstPad * pad,
211 static GstPad *gst_matroska_mux_request_new_pad (GstElement * element,
212 GstPadTemplate * templ, const gchar * name);
213 static void gst_matroska_mux_release_pad (GstElement * element, GstPad * pad);
215 /* gst internal change state handler */
216 static GstStateChangeReturn
217 gst_matroska_mux_change_state (GstElement * element, GstStateChange transition);
219 /* gobject bla bla */
220 static void gst_matroska_mux_set_property (GObject * object,
221 guint prop_id, const GValue * value, GParamSpec * pspec);
222 static void gst_matroska_mux_get_property (GObject * object,
223 guint prop_id, GValue * value, GParamSpec * pspec);
226 static void gst_matroska_mux_reset (GstElement * element);
229 static guint64 gst_matroska_mux_create_uid ();
231 static gboolean theora_streamheader_to_codecdata (const GValue * streamheader,
232 GstMatroskaTrackContext * context);
233 static gboolean vorbis_streamheader_to_codecdata (const GValue * streamheader,
234 GstMatroskaTrackContext * context);
235 static gboolean speex_streamheader_to_codecdata (const GValue * streamheader,
236 GstMatroskaTrackContext * context);
237 static gboolean kate_streamheader_to_codecdata (const GValue * streamheader,
238 GstMatroskaTrackContext * context);
239 static gboolean flac_streamheader_to_codecdata (const GValue * streamheader,
240 GstMatroskaTrackContext * context);
243 gst_matroska_mux_add_interfaces (GType type)
245 static const GInterfaceInfo tag_setter_info = { NULL, NULL, NULL };
247 g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
251 gst_matroska_mux_base_init (gpointer g_class)
253 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
255 gst_element_class_add_pad_template (element_class,
256 gst_static_pad_template_get (&videosink_templ));
257 gst_element_class_add_pad_template (element_class,
258 gst_static_pad_template_get (&audiosink_templ));
259 gst_element_class_add_pad_template (element_class,
260 gst_static_pad_template_get (&subtitlesink_templ));
261 gst_element_class_add_pad_template (element_class,
262 gst_static_pad_template_get (&src_templ));
263 gst_element_class_set_details_simple (element_class, "Matroska muxer",
265 "Muxes video/audio/subtitle streams into a matroska stream",
266 "Ronald Bultje <rbultje@ronald.bitfreak.net>");
268 GST_DEBUG_CATEGORY_INIT (matroskamux_debug, "matroskamux", 0,
273 gst_matroska_mux_class_init (GstMatroskaMuxClass * klass)
275 GObjectClass *gobject_class;
276 GstElementClass *gstelement_class;
278 gobject_class = (GObjectClass *) klass;
279 gstelement_class = (GstElementClass *) klass;
281 gobject_class->finalize = gst_matroska_mux_finalize;
283 gobject_class->get_property = gst_matroska_mux_get_property;
284 gobject_class->set_property = gst_matroska_mux_set_property;
286 g_object_class_install_property (gobject_class, ARG_WRITING_APP,
287 g_param_spec_string ("writing-app", "Writing application.",
288 "The name the application that creates the matroska file.",
289 NULL, G_PARAM_READWRITE));
290 g_object_class_install_property (gobject_class, ARG_MATROSKA_VERSION,
291 g_param_spec_int ("version", "Matroska version",
292 "This parameter determines what matroska features can be used.",
293 1, 2, DEFAULT_MATROSKA_VERSION, G_PARAM_READWRITE));
295 gstelement_class->change_state =
296 GST_DEBUG_FUNCPTR (gst_matroska_mux_change_state);
297 gstelement_class->request_new_pad =
298 GST_DEBUG_FUNCPTR (gst_matroska_mux_request_new_pad);
299 gstelement_class->release_pad =
300 GST_DEBUG_FUNCPTR (gst_matroska_mux_release_pad);
305 * gst_matroska_mux_init:
306 * @mux: #GstMatroskaMux that should be initialized.
307 * @g_class: Class of the muxer.
309 * Matroska muxer constructor.
312 gst_matroska_mux_init (GstMatroskaMux * mux, GstMatroskaMuxClass * g_class)
314 mux->srcpad = gst_pad_new_from_static_template (&src_templ, "src");
315 gst_pad_set_event_function (mux->srcpad, gst_matroska_mux_handle_src_event);
316 gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad);
318 mux->collect = gst_collect_pads_new ();
319 gst_collect_pads_set_function (mux->collect,
320 (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_matroska_mux_collected),
323 mux->ebml_write = gst_ebml_write_new (mux->srcpad);
325 /* property defaults */
326 mux->matroska_version = DEFAULT_MATROSKA_VERSION;
327 mux->writing_app = g_strdup (DEFAULT_WRITING_APP);
329 /* initialize internal variables */
331 mux->num_streams = 0;
332 mux->num_a_streams = 0;
333 mux->num_t_streams = 0;
334 mux->num_v_streams = 0;
336 /* initialize remaining variables */
337 gst_matroska_mux_reset (GST_ELEMENT (mux));
342 * gst_matroska_mux_finalize:
343 * @object: #GstMatroskaMux that should be finalized.
345 * Finalize matroska muxer.
348 gst_matroska_mux_finalize (GObject * object)
350 GstMatroskaMux *mux = GST_MATROSKA_MUX (object);
352 gst_object_unref (mux->collect);
353 gst_object_unref (mux->ebml_write);
354 if (mux->writing_app)
355 g_free (mux->writing_app);
357 G_OBJECT_CLASS (parent_class)->finalize (object);
362 * gst_matroska_mux_create_uid:
364 * Generate new unused track UID.
366 * Returns: New track UID.
369 gst_matroska_mux_create_uid (void)
376 used_uids = g_array_sized_new (FALSE, FALSE, sizeof (guint64), 10);
381 uid = (((guint64) g_random_int ()) << 32) | g_random_int ();
382 for (i = 0; i < used_uids->len; i++) {
383 if (g_array_index (used_uids, guint64, i) == uid) {
388 g_array_append_val (used_uids, uid);
391 G_UNLOCK (used_uids);
397 * gst_matroska_pad_reset:
398 * @collect_pad: the #GstMatroskaPad
400 * Reset and/or release resources of a matroska collect pad.
403 gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full)
406 GstMatroskaTrackType type = 0;
408 /* free track information */
409 if (collect_pad->track != NULL) {
410 /* retrieve for optional later use */
411 name = collect_pad->track->name;
412 type = collect_pad->track->type;
413 /* extra for video */
414 if (type == GST_MATROSKA_TRACK_TYPE_VIDEO) {
415 GstMatroskaTrackVideoContext *ctx =
416 (GstMatroskaTrackVideoContext *) collect_pad->track;
418 if (ctx->dirac_unit) {
419 gst_buffer_unref (ctx->dirac_unit);
420 ctx->dirac_unit = NULL;
423 g_free (collect_pad->track->codec_id);
424 g_free (collect_pad->track->codec_name);
426 g_free (collect_pad->track->name);
427 g_free (collect_pad->track->language);
428 g_free (collect_pad->track->codec_priv);
429 g_free (collect_pad->track);
430 collect_pad->track = NULL;
433 /* free cached buffer */
434 if (collect_pad->buffer != NULL) {
435 gst_buffer_unref (collect_pad->buffer);
436 collect_pad->buffer = NULL;
439 if (!full && type != 0) {
440 GstMatroskaTrackContext *context;
442 /* create a fresh context */
444 case GST_MATROSKA_TRACK_TYPE_VIDEO:
445 context = (GstMatroskaTrackContext *)
446 g_new0 (GstMatroskaTrackVideoContext, 1);
448 case GST_MATROSKA_TRACK_TYPE_AUDIO:
449 context = (GstMatroskaTrackContext *)
450 g_new0 (GstMatroskaTrackAudioContext, 1);
452 case GST_MATROSKA_TRACK_TYPE_SUBTITLE:
453 context = (GstMatroskaTrackContext *)
454 g_new0 (GstMatroskaTrackSubtitleContext, 1);
457 g_assert_not_reached ();
461 context->type = type;
462 context->name = name;
463 /* TODO: check default values for the context */
464 context->flags = GST_MATROSKA_TRACK_ENABLED | GST_MATROSKA_TRACK_DEFAULT;
465 collect_pad->track = context;
466 collect_pad->buffer = NULL;
467 collect_pad->duration = 0;
468 collect_pad->start_ts = GST_CLOCK_TIME_NONE;
469 collect_pad->end_ts = GST_CLOCK_TIME_NONE;
474 * gst_matroska_pad_free:
475 * @collect_pad: the #GstMatroskaPad
477 * Release resources of a matroska collect pad.
480 gst_matroska_pad_free (GstMatroskaPad * collect_pad)
482 gst_matroska_pad_reset (collect_pad, TRUE);
487 * gst_matroska_mux_reset:
488 * @element: #GstMatroskaMux that should be reseted.
490 * Reset matroska muxer back to initial state.
493 gst_matroska_mux_reset (GstElement * element)
495 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
498 /* reset EBML write */
499 gst_ebml_write_reset (mux->ebml_write);
502 mux->state = GST_MATROSKA_MUX_STATE_START;
504 /* clean up existing streams */
506 for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
507 GstMatroskaPad *collect_pad;
509 collect_pad = (GstMatroskaPad *) walk->data;
511 /* reset collect pad to pristine state */
512 gst_matroska_pad_reset (collect_pad, FALSE);
516 mux->num_indexes = 0;
521 mux->time_scale = GST_MSECOND;
526 mux->cluster_time = 0;
527 mux->cluster_pos = 0;
530 gst_tag_setter_reset_tags (GST_TAG_SETTER (mux));
534 * gst_matroska_mux_handle_src_event:
535 * @pad: Pad which received the event.
536 * @event: Received event.
538 * handle events - copied from oggmux without understanding
540 * Returns: #TRUE on success.
543 gst_matroska_mux_handle_src_event (GstPad * pad, GstEvent * event)
547 type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;
551 /* disable seeking for now */
557 return gst_pad_event_default (pad, event);
561 * gst_matroska_mux_handle_sink_event:
562 * @pad: Pad which received the event.
563 * @event: Received event.
565 * handle events - informational ones like tags
567 * Returns: #TRUE on success.
570 gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event)
572 GstMatroskaTrackContext *context;
573 GstMatroskaPad *collect_pad;
578 mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad));
580 switch (GST_EVENT_TYPE (event)) {
582 GST_DEBUG_OBJECT (mux, "received tag event");
583 gst_event_parse_tag (event, &list);
585 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
586 g_assert (collect_pad);
587 context = collect_pad->track;
590 * strictly speaking, the incoming language code may only be 639-1, so not
591 * 639-2 according to matroska specs, but it will have to do for now */
592 gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &context->language);
594 gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), list,
595 gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (mux)));
597 case GST_EVENT_NEWSEGMENT:
598 /* We don't support NEWSEGMENT events */
600 gst_event_unref (event);
606 /* now GstCollectPads can take care of the rest, e.g. EOS */
608 ret = mux->collect_event (pad, event);
609 gst_object_unref (mux);
616 * gst_matroska_mux_video_pad_setcaps:
617 * @pad: Pad which got the caps.
620 * Setcaps function for video sink pad.
622 * Returns: #TRUE on success.
625 gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
627 GstMatroskaTrackContext *context = NULL;
628 GstMatroskaTrackVideoContext *videocontext;
630 GstMatroskaPad *collect_pad;
631 GstStructure *structure;
632 const gchar *mimetype;
633 const GValue *value = NULL;
634 const GstBuffer *codec_buf = NULL;
635 gint width, height, pixel_width, pixel_height;
638 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
641 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
642 g_assert (collect_pad);
643 context = collect_pad->track;
645 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO);
646 videocontext = (GstMatroskaTrackVideoContext *) context;
648 /* gst -> matroska ID'ing */
649 structure = gst_caps_get_structure (caps, 0);
651 mimetype = gst_structure_get_name (structure);
653 if (!strcmp (mimetype, "video/x-theora")) {
654 /* we'll extract the details later from the theora identification header */
658 /* get general properties */
659 gst_structure_get_int (structure, "width", &width);
660 gst_structure_get_int (structure, "height", &height);
661 videocontext->pixel_width = width;
662 videocontext->pixel_height = height;
663 if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d)
665 context->default_duration =
666 gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n);
667 GST_LOG_OBJECT (pad, "default duration = %" GST_TIME_FORMAT,
668 GST_TIME_ARGS (context->default_duration));
670 context->default_duration = 0;
672 if (gst_structure_get_fraction (structure, "pixel-aspect-ratio",
673 &pixel_width, &pixel_height)) {
674 if (pixel_width > pixel_height) {
675 videocontext->display_width = width * pixel_width / pixel_height;
676 videocontext->display_height = height;
677 } else if (pixel_width < pixel_height) {
678 videocontext->display_width = width;
679 videocontext->display_height = height * pixel_height / pixel_width;
681 videocontext->display_width = 0;
682 videocontext->display_height = 0;
685 videocontext->display_width = 0;
686 videocontext->display_height = 0;
691 videocontext->asr_mode = GST_MATROSKA_ASPECT_RATIO_MODE_FREE;
692 videocontext->fourcc = 0;
694 /* TODO: - check if we handle all codecs by the spec, i.e. codec private
695 * data and other settings
699 /* extract codec_data, may turn out needed */
700 value = gst_structure_get_value (structure, "codec_data");
702 codec_buf = gst_value_get_buffer (value);
705 if (!strcmp (mimetype, "video/x-raw-yuv")) {
706 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED);
707 gst_structure_get_fourcc (structure, "format", &videocontext->fourcc);
710 } else if (!strcmp (mimetype, "image/jpeg")) {
711 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
714 } else if (!strcmp (mimetype, "video/x-xvid") /* MS/VfW compatibility cases */
715 ||!strcmp (mimetype, "video/x-huffyuv")
716 || !strcmp (mimetype, "video/x-divx")
717 || !strcmp (mimetype, "video/x-dv")
718 || !strcmp (mimetype, "video/x-h263")
719 || !strcmp (mimetype, "video/x-msmpeg")
720 || !strcmp (mimetype, "video/x-wmv")) {
721 BITMAPINFOHEADER *bih;
722 gint size = sizeof (BITMAPINFOHEADER);
725 if (!strcmp (mimetype, "video/x-xvid"))
726 fourcc = GST_MAKE_FOURCC ('X', 'V', 'I', 'D');
727 else if (!strcmp (mimetype, "video/x-huffyuv"))
728 fourcc = GST_MAKE_FOURCC ('H', 'F', 'Y', 'U');
729 else if (!strcmp (mimetype, "video/x-dv"))
730 fourcc = GST_MAKE_FOURCC ('D', 'V', 'S', 'D');
731 else if (!strcmp (mimetype, "video/x-h263"))
732 fourcc = GST_MAKE_FOURCC ('H', '2', '6', '3');
733 else if (!strcmp (mimetype, "video/x-divx")) {
736 gst_structure_get_int (structure, "divxversion", &divxversion);
737 switch (divxversion) {
739 fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', '3');
742 fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', 'X');
745 fourcc = GST_MAKE_FOURCC ('D', 'X', '5', '0');
748 } else if (!strcmp (mimetype, "video/x-msmpeg")) {
751 gst_structure_get_int (structure, "msmpegversion", &msmpegversion);
752 switch (msmpegversion) {
754 fourcc = GST_MAKE_FOURCC ('M', 'P', 'G', '4');
757 fourcc = GST_MAKE_FOURCC ('M', 'P', '4', '2');
763 } else if (!strcmp (mimetype, "video/x-wmv")) {
766 if (gst_structure_get_fourcc (structure, "format", &format)) {
768 } else if (gst_structure_get_int (structure, "wmvversion", &wmvversion)) {
769 if (wmvversion == 2) {
770 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '2');
771 } else if (wmvversion == 1) {
772 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '1');
773 } else if (wmvversion == 3) {
774 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '3');
782 bih = g_new0 (BITMAPINFOHEADER, 1);
783 GST_WRITE_UINT32_LE (&bih->bi_size, size);
784 GST_WRITE_UINT32_LE (&bih->bi_width, videocontext->pixel_width);
785 GST_WRITE_UINT32_LE (&bih->bi_height, videocontext->pixel_height);
786 GST_WRITE_UINT32_LE (&bih->bi_compression, fourcc);
787 GST_WRITE_UINT16_LE (&bih->bi_planes, (guint16) 1);
788 GST_WRITE_UINT16_LE (&bih->bi_bit_count, (guint16) 24);
789 GST_WRITE_UINT32_LE (&bih->bi_size_image, videocontext->pixel_width *
790 videocontext->pixel_height * 3);
792 /* process codec private/initialization data, if any */
794 size += GST_BUFFER_SIZE (codec_buf);
795 bih = g_realloc (bih, size);
796 GST_WRITE_UINT32_LE (&bih->bi_size, size);
797 memcpy ((guint8 *) bih + sizeof (BITMAPINFOHEADER),
798 GST_BUFFER_DATA (codec_buf), GST_BUFFER_SIZE (codec_buf));
801 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
802 context->codec_priv = (gpointer) bih;
803 context->codec_priv_size = size;
806 } else if (!strcmp (mimetype, "video/x-h264")) {
807 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC);
809 if (context->codec_priv != NULL) {
810 g_free (context->codec_priv);
811 context->codec_priv = NULL;
812 context->codec_priv_size = 0;
815 /* Create avcC header */
816 if (codec_buf != NULL) {
817 context->codec_priv_size = GST_BUFFER_SIZE (codec_buf);
818 context->codec_priv = g_malloc0 (context->codec_priv_size);
819 memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
820 context->codec_priv_size);
824 } else if (!strcmp (mimetype, "video/x-theora")) {
825 const GValue *streamheader;
827 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_THEORA);
829 if (context->codec_priv != NULL) {
830 g_free (context->codec_priv);
831 context->codec_priv = NULL;
832 context->codec_priv_size = 0;
835 streamheader = gst_structure_get_value (structure, "streamheader");
836 if (!theora_streamheader_to_codecdata (streamheader, context)) {
837 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
838 ("theora stream headers missing or malformed"));
842 } else if (!strcmp (mimetype, "video/x-dirac")) {
843 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_DIRAC);
846 } else if (!strcmp (mimetype, "video/mpeg")) {
849 gst_structure_get_int (structure, "mpegversion", &mpegversion);
850 switch (mpegversion) {
852 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG1);
855 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG2);
858 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
864 /* global headers may be in codec data */
865 if (codec_buf != NULL) {
866 context->codec_priv_size = GST_BUFFER_SIZE (codec_buf);
867 context->codec_priv = g_malloc0 (context->codec_priv_size);
868 memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
869 context->codec_priv_size);
873 } else if (!strcmp (mimetype, "video/x-msmpeg")) {
875 /* can only make it here if preceding case verified it was version 3 */
876 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3);
879 } else if (!strcmp (mimetype, "video/x-pn-realvideo")) {
881 const GValue *mdpr_data;
883 gst_structure_get_int (structure, "rmversion", &rmversion);
886 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1);
889 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2);
892 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3);
895 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4);
901 mdpr_data = gst_structure_get_value (structure, "mdpr_data");
902 if (mdpr_data != NULL) {
903 guint8 *priv_data = NULL;
904 guint priv_data_size = 0;
906 GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data);
908 priv_data_size = GST_BUFFER_SIZE (codec_data_buf);
909 priv_data = g_malloc0 (priv_data_size);
911 memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size);
913 context->codec_priv = priv_data;
914 context->codec_priv_size = priv_data_size;
923 /* N > 0 to expect a particular number of headers, negative if the
924 number of headers is variable */
926 xiphN_streamheader_to_codecdata (const GValue * streamheader,
927 GstMatroskaTrackContext * context, GstBuffer ** p_buf0, int N)
929 GstBuffer **buf = NULL;
932 guint bufi, i, offset, priv_data_size;
934 if (streamheader == NULL)
935 goto no_stream_headers;
937 if (G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY)
940 bufarr = g_value_peek_pointer (streamheader);
941 if (bufarr->len <= 0 || bufarr->len > 255) /* at least one header, and count stored in a byte */
943 if (N > 0 && bufarr->len != N)
946 context->xiph_headers_to_skip = bufarr->len;
948 buf = (GstBuffer **) g_malloc0 (sizeof (GstBuffer *) * bufarr->len);
949 for (i = 0; i < bufarr->len; i++) {
950 GValue *bufval = &g_array_index (bufarr, GValue, i);
952 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
954 goto wrong_content_type;
957 buf[i] = g_value_peek_pointer (bufval);
961 if (bufarr->len > 0) {
962 for (i = 0; i < bufarr->len - 1; i++) {
963 priv_data_size += GST_BUFFER_SIZE (buf[i]) / 0xff + 1;
967 for (i = 0; i < bufarr->len; ++i) {
968 priv_data_size += GST_BUFFER_SIZE (buf[i]);
971 priv_data = g_malloc0 (priv_data_size);
973 priv_data[0] = bufarr->len - 1;
976 if (bufarr->len > 0) {
977 for (bufi = 0; bufi < bufarr->len - 1; bufi++) {
978 for (i = 0; i < GST_BUFFER_SIZE (buf[bufi]) / 0xff; ++i) {
979 priv_data[offset++] = 0xff;
981 priv_data[offset++] = GST_BUFFER_SIZE (buf[bufi]) % 0xff;
985 for (i = 0; i < bufarr->len; ++i) {
986 memcpy (priv_data + offset, GST_BUFFER_DATA (buf[i]),
987 GST_BUFFER_SIZE (buf[i]));
988 offset += GST_BUFFER_SIZE (buf[i]);
991 context->codec_priv = priv_data;
992 context->codec_priv_size = priv_data_size;
995 *p_buf0 = gst_buffer_ref (buf[0]);
1004 GST_WARNING ("required streamheaders missing in sink caps!");
1009 GST_WARNING ("streamheaders are not a GST_TYPE_ARRAY, but a %s",
1010 G_VALUE_TYPE_NAME (streamheader));
1015 GST_WARNING ("got %u streamheaders, not 3 as expected", bufarr->len);
1020 GST_WARNING ("streamheaders array does not contain GstBuffers");
1025 /* FIXME: after release make all code use xiph3_streamheader_to_codecdata() */
1027 xiph3_streamheader_to_codecdata (const GValue * streamheader,
1028 GstMatroskaTrackContext * context, GstBuffer ** p_buf0)
1033 guint i, offset, priv_data_size;
1035 if (streamheader == NULL)
1036 goto no_stream_headers;
1038 if (G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY)
1041 bufarr = g_value_peek_pointer (streamheader);
1042 if (bufarr->len != 3)
1045 context->xiph_headers_to_skip = bufarr->len;
1047 for (i = 0; i < 3; i++) {
1048 GValue *bufval = &g_array_index (bufarr, GValue, i);
1050 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER)
1051 goto wrong_content_type;
1053 buf[i] = g_value_peek_pointer (bufval);
1057 priv_data_size += GST_BUFFER_SIZE (buf[0]) / 0xff + 1;
1058 priv_data_size += GST_BUFFER_SIZE (buf[1]) / 0xff + 1;
1060 for (i = 0; i < 3; ++i) {
1061 priv_data_size += GST_BUFFER_SIZE (buf[i]);
1064 priv_data = g_malloc0 (priv_data_size);
1069 for (i = 0; i < GST_BUFFER_SIZE (buf[0]) / 0xff; ++i) {
1070 priv_data[offset++] = 0xff;
1072 priv_data[offset++] = GST_BUFFER_SIZE (buf[0]) % 0xff;
1074 for (i = 0; i < GST_BUFFER_SIZE (buf[1]) / 0xff; ++i) {
1075 priv_data[offset++] = 0xff;
1077 priv_data[offset++] = GST_BUFFER_SIZE (buf[1]) % 0xff;
1079 for (i = 0; i < 3; ++i) {
1080 memcpy (priv_data + offset, GST_BUFFER_DATA (buf[i]),
1081 GST_BUFFER_SIZE (buf[i]));
1082 offset += GST_BUFFER_SIZE (buf[i]);
1085 context->codec_priv = priv_data;
1086 context->codec_priv_size = priv_data_size;
1089 *p_buf0 = gst_buffer_ref (buf[0]);
1096 GST_WARNING ("required streamheaders missing in sink caps!");
1101 GST_WARNING ("streamheaders are not a GST_TYPE_ARRAY, but a %s",
1102 G_VALUE_TYPE_NAME (streamheader));
1107 GST_WARNING ("got %u streamheaders, not 3 as expected", bufarr->len);
1112 GST_WARNING ("streamheaders array does not contain GstBuffers");
1118 vorbis_streamheader_to_codecdata (const GValue * streamheader,
1119 GstMatroskaTrackContext * context)
1121 GstBuffer *buf0 = NULL;
1123 /* FIXME: change to use xiphN_streamheader_to_codecdata() after release */
1124 if (!xiph3_streamheader_to_codecdata (streamheader, context, &buf0))
1127 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 4) {
1128 GST_WARNING ("First vorbis header too small, ignoring");
1130 if (memcmp (GST_BUFFER_DATA (buf0) + 1, "vorbis", 6) == 0) {
1131 GstMatroskaTrackAudioContext *audiocontext;
1134 hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 4;
1135 audiocontext = (GstMatroskaTrackAudioContext *) context;
1136 audiocontext->channels = GST_READ_UINT8 (hdr);
1137 audiocontext->samplerate = GST_READ_UINT32_LE (hdr + 1);
1142 gst_buffer_unref (buf0);
1148 theora_streamheader_to_codecdata (const GValue * streamheader,
1149 GstMatroskaTrackContext * context)
1151 GstBuffer *buf0 = NULL;
1153 /* FIXME: change to use xiphN_streamheader_to_codecdata() after release */
1154 if (!xiph3_streamheader_to_codecdata (streamheader, context, &buf0))
1157 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 26) {
1158 GST_WARNING ("First theora header too small, ignoring");
1159 } else if (memcmp (GST_BUFFER_DATA (buf0), "\200theora\003\002", 9) != 0) {
1160 GST_WARNING ("First header not a theora identification header, ignoring");
1162 GstMatroskaTrackVideoContext *videocontext;
1163 guint fps_num, fps_denom, par_num, par_denom;
1166 hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 3 + 2 + 2;
1168 videocontext = (GstMatroskaTrackVideoContext *) context;
1169 videocontext->pixel_width = GST_READ_UINT32_BE (hdr) >> 8;
1170 videocontext->pixel_height = GST_READ_UINT32_BE (hdr + 3) >> 8;
1171 hdr += 3 + 3 + 1 + 1;
1172 fps_num = GST_READ_UINT32_BE (hdr);
1173 fps_denom = GST_READ_UINT32_BE (hdr + 4);
1174 context->default_duration = gst_util_uint64_scale_int (GST_SECOND,
1175 fps_denom, fps_num);
1177 par_num = GST_READ_UINT32_BE (hdr) >> 8;
1178 par_denom = GST_READ_UINT32_BE (hdr + 3) >> 8;
1179 if (par_num > 0 && par_num > 0) {
1180 if (par_num > par_denom) {
1181 videocontext->display_width =
1182 videocontext->pixel_width * par_num / par_denom;
1183 videocontext->display_height = videocontext->pixel_height;
1184 } else if (par_num < par_denom) {
1185 videocontext->display_width = videocontext->pixel_width;
1186 videocontext->display_height =
1187 videocontext->pixel_height * par_denom / par_num;
1189 videocontext->display_width = 0;
1190 videocontext->display_height = 0;
1193 videocontext->display_width = 0;
1194 videocontext->display_height = 0;
1200 gst_buffer_unref (buf0);
1206 kate_streamheader_to_codecdata (const GValue * streamheader,
1207 GstMatroskaTrackContext * context)
1209 GstBuffer *buf0 = NULL;
1211 if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, -1))
1214 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 64) { /* Kate ID header is 64 bytes */
1215 GST_WARNING ("First kate header too small, ignoring");
1216 } else if (memcmp (GST_BUFFER_DATA (buf0), "\200kate\0\0\0", 8) != 0) {
1217 GST_WARNING ("First header not a kate identification header, ignoring");
1221 gst_buffer_unref (buf0);
1227 flac_streamheader_to_codecdata (const GValue * streamheader,
1228 GstMatroskaTrackContext * context)
1235 if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) {
1236 GST_WARNING ("No or invalid streamheader field in the caps");
1240 bufarr = g_value_peek_pointer (streamheader);
1241 if (bufarr->len < 2) {
1242 GST_WARNING ("Too few headers in streamheader field");
1246 context->xiph_headers_to_skip = bufarr->len + 1;
1248 bufval = &g_array_index (bufarr, GValue, 0);
1249 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1250 GST_WARNING ("streamheaders array does not contain GstBuffers");
1254 buffer = g_value_peek_pointer (bufval);
1256 /* Need at least OggFLAC mapping header, fLaC marker and STREAMINFO block */
1257 if (GST_BUFFER_SIZE (buffer) < 9 + 4 + 4 + 34
1258 || memcmp (GST_BUFFER_DATA (buffer) + 1, "FLAC", 4) != 0
1259 || memcmp (GST_BUFFER_DATA (buffer) + 9, "fLaC", 4) != 0) {
1260 GST_WARNING ("Invalid streamheader for FLAC");
1264 context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer) - 9);
1265 context->codec_priv_size = GST_BUFFER_SIZE (buffer) - 9;
1266 memcpy (context->codec_priv, GST_BUFFER_DATA (buffer) + 9,
1267 GST_BUFFER_SIZE (buffer) - 9);
1269 for (i = 1; i < bufarr->len; i++) {
1270 bufval = &g_array_index (bufarr, GValue, i);
1272 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1273 g_free (context->codec_priv);
1274 context->codec_priv = NULL;
1275 context->codec_priv_size = 0;
1276 GST_WARNING ("streamheaders array does not contain GstBuffers");
1280 buffer = g_value_peek_pointer (bufval);
1282 context->codec_priv =
1283 g_realloc (context->codec_priv,
1284 context->codec_priv_size + GST_BUFFER_SIZE (buffer));
1285 memcpy ((guint8 *) context->codec_priv + context->codec_priv_size,
1286 GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
1287 context->codec_priv_size =
1288 context->codec_priv_size + GST_BUFFER_SIZE (buffer);
1295 speex_streamheader_to_codecdata (const GValue * streamheader,
1296 GstMatroskaTrackContext * context)
1302 if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) {
1303 GST_WARNING ("No or invalid streamheader field in the caps");
1307 bufarr = g_value_peek_pointer (streamheader);
1308 if (bufarr->len != 2) {
1309 GST_WARNING ("Too few headers in streamheader field");
1313 context->xiph_headers_to_skip = bufarr->len + 1;
1315 bufval = &g_array_index (bufarr, GValue, 0);
1316 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1317 GST_WARNING ("streamheaders array does not contain GstBuffers");
1321 buffer = g_value_peek_pointer (bufval);
1323 if (GST_BUFFER_SIZE (buffer) < 80
1324 || memcmp (GST_BUFFER_DATA (buffer), "Speex ", 8) != 0) {
1325 GST_WARNING ("Invalid streamheader for Speex");
1329 context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer));
1330 context->codec_priv_size = GST_BUFFER_SIZE (buffer);
1331 memcpy (context->codec_priv, GST_BUFFER_DATA (buffer),
1332 GST_BUFFER_SIZE (buffer));
1334 bufval = &g_array_index (bufarr, GValue, 1);
1336 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1337 g_free (context->codec_priv);
1338 context->codec_priv = NULL;
1339 context->codec_priv_size = 0;
1340 GST_WARNING ("streamheaders array does not contain GstBuffers");
1344 buffer = g_value_peek_pointer (bufval);
1346 context->codec_priv =
1347 g_realloc (context->codec_priv,
1348 context->codec_priv_size + GST_BUFFER_SIZE (buffer));
1349 memcpy ((guint8 *) context->codec_priv + context->codec_priv_size,
1350 GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
1351 context->codec_priv_size =
1352 context->codec_priv_size + GST_BUFFER_SIZE (buffer);
1358 aac_codec_data_to_codec_id (const GstBuffer * buf)
1363 /* default to MAIN */
1366 if (GST_BUFFER_SIZE (buf) >= 2) {
1367 profile = GST_READ_UINT8 (GST_BUFFER_DATA (buf));
1385 GST_WARNING ("unknown AAC profile, defaulting to MAIN");
1394 * gst_matroska_mux_audio_pad_setcaps:
1395 * @pad: Pad which got the caps.
1398 * Setcaps function for audio sink pad.
1400 * Returns: #TRUE on success.
1403 gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
1405 GstMatroskaTrackContext *context = NULL;
1406 GstMatroskaTrackAudioContext *audiocontext;
1407 GstMatroskaMux *mux;
1408 GstMatroskaPad *collect_pad;
1409 const gchar *mimetype;
1410 gint samplerate = 0, channels = 0;
1411 GstStructure *structure;
1412 const GValue *codec_data = NULL;
1413 const GstBuffer *buf = NULL;
1415 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1418 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
1419 g_assert (collect_pad);
1420 context = collect_pad->track;
1422 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_AUDIO);
1423 audiocontext = (GstMatroskaTrackAudioContext *) context;
1425 structure = gst_caps_get_structure (caps, 0);
1426 mimetype = gst_structure_get_name (structure);
1429 gst_structure_get_int (structure, "rate", &samplerate);
1430 gst_structure_get_int (structure, "channels", &channels);
1432 audiocontext->samplerate = samplerate;
1433 audiocontext->channels = channels;
1434 audiocontext->bitdepth = 0;
1435 context->default_duration = 0;
1437 codec_data = gst_structure_get_value (structure, "codec_data");
1439 buf = gst_value_get_buffer (codec_data);
1441 /* TODO: - check if we handle all codecs by the spec, i.e. codec private
1442 * data and other settings
1446 if (!strcmp (mimetype, "audio/mpeg")) {
1447 gint mpegversion = 0;
1449 gst_structure_get_int (structure, "mpegversion", &mpegversion);
1450 switch (mpegversion) {
1456 gst_structure_get_int (structure, "layer", &layer);
1458 if (!gst_structure_get_int (structure, "mpegaudioversion", &version)) {
1459 GST_WARNING_OBJECT (mux,
1460 "Unable to determine MPEG audio version, assuming 1");
1466 else if (layer == 2)
1468 else if (version == 2)
1473 context->default_duration =
1474 gst_util_uint64_scale (GST_SECOND, spf, audiocontext->samplerate);
1478 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1);
1481 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2);
1484 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3);
1494 g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG2 "%s",
1495 aac_codec_data_to_codec_id (buf));
1497 GST_DEBUG_OBJECT (mux, "no AAC codec_data; not packetized");
1504 g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4 "%s",
1505 aac_codec_data_to_codec_id (buf));
1507 GST_DEBUG_OBJECT (mux, "no AAC codec_data; not packetized");
1516 } else if (!strcmp (mimetype, "audio/x-raw-int")) {
1518 gint endianness = G_LITTLE_ENDIAN;
1519 gboolean signedness = TRUE;
1521 if (!gst_structure_get_int (structure, "width", &width) ||
1522 !gst_structure_get_int (structure, "depth", &depth) ||
1523 !gst_structure_get_boolean (structure, "signed", &signedness)) {
1524 GST_DEBUG_OBJECT (mux, "broken caps, width/depth/signed field missing");
1529 !gst_structure_get_int (structure, "endianness", &endianness)) {
1530 GST_DEBUG_OBJECT (mux, "broken caps, no endianness specified");
1534 if (width != depth) {
1535 GST_DEBUG_OBJECT (mux, "width must be same as depth!");
1539 /* FIXME: where is this spec'ed out? (tpm) */
1540 if ((width == 8 && signedness) || (width >= 16 && !signedness)) {
1541 GST_DEBUG_OBJECT (mux, "8-bit PCM must be unsigned, 16-bit PCM signed");
1545 audiocontext->bitdepth = depth;
1546 if (endianness == G_BIG_ENDIAN)
1547 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE);
1549 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE);
1552 } else if (!strcmp (mimetype, "audio/x-raw-float")) {
1555 if (!gst_structure_get_int (structure, "width", &width)) {
1556 GST_DEBUG_OBJECT (mux, "broken caps, width field missing");
1560 audiocontext->bitdepth = width;
1561 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT);
1564 } else if (!strcmp (mimetype, "audio/x-vorbis")) {
1565 const GValue *streamheader;
1567 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_VORBIS);
1569 if (context->codec_priv != NULL) {
1570 g_free (context->codec_priv);
1571 context->codec_priv = NULL;
1572 context->codec_priv_size = 0;
1575 streamheader = gst_structure_get_value (structure, "streamheader");
1576 if (!vorbis_streamheader_to_codecdata (streamheader, context)) {
1577 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1578 ("vorbis stream headers missing or malformed"));
1582 } else if (!strcmp (mimetype, "audio/x-flac")) {
1583 const GValue *streamheader;
1585 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_FLAC);
1586 if (context->codec_priv != NULL) {
1587 g_free (context->codec_priv);
1588 context->codec_priv = NULL;
1589 context->codec_priv_size = 0;
1592 streamheader = gst_structure_get_value (structure, "streamheader");
1593 if (!flac_streamheader_to_codecdata (streamheader, context)) {
1594 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1595 ("flac stream headers missing or malformed"));
1599 } else if (!strcmp (mimetype, "audio/x-speex")) {
1600 const GValue *streamheader;
1602 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_SPEEX);
1603 if (context->codec_priv != NULL) {
1604 g_free (context->codec_priv);
1605 context->codec_priv = NULL;
1606 context->codec_priv_size = 0;
1609 streamheader = gst_structure_get_value (structure, "streamheader");
1610 if (!speex_streamheader_to_codecdata (streamheader, context)) {
1611 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1612 ("speex stream headers missing or malformed"));
1616 } else if (!strcmp (mimetype, "audio/x-ac3")) {
1617 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3);
1620 } else if (!strcmp (mimetype, "audio/x-tta")) {
1623 /* TTA frame duration */
1624 context->default_duration = 1.04489795918367346939 * GST_SECOND;
1626 gst_structure_get_int (structure, "width", &width);
1627 audiocontext->bitdepth = width;
1628 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_TTA);
1631 } else if (!strcmp (mimetype, "audio/x-pn-realaudio")) {
1633 const GValue *mdpr_data;
1635 gst_structure_get_int (structure, "raversion", &raversion);
1636 switch (raversion) {
1638 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4);
1641 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_28_8);
1644 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK);
1650 mdpr_data = gst_structure_get_value (structure, "mdpr_data");
1651 if (mdpr_data != NULL) {
1652 guint8 *priv_data = NULL;
1653 guint priv_data_size = 0;
1655 GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data);
1657 priv_data_size = GST_BUFFER_SIZE (codec_data_buf);
1658 priv_data = g_malloc0 (priv_data_size);
1660 memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size);
1662 context->codec_priv = priv_data;
1663 context->codec_priv_size = priv_data_size;
1667 } else if (!strcmp (mimetype, "audio/x-wma")) {
1669 guint codec_priv_size;
1676 if (!gst_structure_get_int (structure, "wmaversion", &wmaversion)
1677 || !gst_structure_get_int (structure, "block_align", &block_align)
1678 || !gst_structure_get_int (structure, "bitrate", &bitrate)
1679 || samplerate == 0 || channels == 0) {
1680 GST_WARNING_OBJECT (mux, "Missing wmaversion/block_align/bitrate/"
1681 "channels/rate on WMA caps");
1685 switch (wmaversion) {
1687 format = GST_RIFF_WAVE_FORMAT_WMAV1;
1690 format = GST_RIFF_WAVE_FORMAT_WMAV2;
1693 format = GST_RIFF_WAVE_FORMAT_WMAV3;
1696 GST_WARNING_OBJECT (mux, "Unexpected WMA version: %d", wmaversion);
1700 if (gst_structure_get_int (structure, "depth", &depth))
1701 audiocontext->bitdepth = depth;
1703 codec_priv_size = WAVEFORMATEX_SIZE;
1705 codec_priv_size += GST_BUFFER_SIZE (buf);
1707 /* serialize waveformatex structure */
1708 codec_priv = g_malloc0 (codec_priv_size);
1709 GST_WRITE_UINT16_LE (codec_priv, format);
1710 GST_WRITE_UINT16_LE (codec_priv + 2, channels);
1711 GST_WRITE_UINT32_LE (codec_priv + 4, samplerate);
1712 GST_WRITE_UINT32_LE (codec_priv + 8, bitrate / 8);
1713 GST_WRITE_UINT16_LE (codec_priv + 12, block_align);
1714 GST_WRITE_UINT16_LE (codec_priv + 14, 0);
1716 GST_WRITE_UINT16_LE (codec_priv + 16, GST_BUFFER_SIZE (buf));
1718 GST_WRITE_UINT16_LE (codec_priv + 16, 0);
1720 /* process codec private/initialization data, if any */
1722 memcpy ((guint8 *) codec_priv + WAVEFORMATEX_SIZE,
1723 GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
1726 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_ACM);
1727 context->codec_priv = (gpointer) codec_priv;
1728 context->codec_priv_size = codec_priv_size;
1737 * gst_matroska_mux_subtitle_pad_setcaps:
1738 * @pad: Pad which got the caps.
1741 * Setcaps function for subtitle sink pad.
1743 * Returns: #TRUE on success.
1746 gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps)
1749 * Consider this as boilerplate code for now. There is
1750 * no single subtitle creation element in GStreamer,
1751 * neither do I know how subtitling works at all. */
1753 /* There is now (at least) one such alement (kateenc), and I'm going
1754 to handle it here and claim it works when it can be piped back
1755 through GStreamer and VLC */
1757 GstMatroskaTrackContext *context = NULL;
1758 GstMatroskaTrackSubtitleContext *scontext;
1759 GstMatroskaMux *mux;
1760 GstMatroskaPad *collect_pad;
1761 const gchar *mimetype;
1762 GstStructure *structure;
1764 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1767 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
1768 g_assert (collect_pad);
1769 context = collect_pad->track;
1771 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_SUBTITLE);
1772 scontext = (GstMatroskaTrackSubtitleContext *) context;
1774 structure = gst_caps_get_structure (caps, 0);
1775 mimetype = gst_structure_get_name (structure);
1778 scontext->check_utf8 = 1;
1779 scontext->invalid_utf8 = 0;
1780 context->default_duration = 0;
1782 /* TODO: - other format than Kate */
1784 if (!strcmp (mimetype, "subtitle/x-kate")) {
1785 const GValue *streamheader;
1787 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_SUBTITLE_KATE);
1789 if (context->codec_priv != NULL) {
1790 g_free (context->codec_priv);
1791 context->codec_priv = NULL;
1792 context->codec_priv_size = 0;
1795 streamheader = gst_structure_get_value (structure, "streamheader");
1796 if (!kate_streamheader_to_codecdata (streamheader, context)) {
1797 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1798 ("kate stream headers missing or malformed"));
1809 * gst_matroska_mux_request_new_pad:
1810 * @element: #GstMatroskaMux.
1811 * @templ: #GstPadTemplate.
1812 * @pad_name: New pad name.
1814 * Request pad function for sink templates.
1816 * Returns: New #GstPad.
1819 gst_matroska_mux_request_new_pad (GstElement * element,
1820 GstPadTemplate * templ, const gchar * pad_name)
1822 GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
1823 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
1824 GstMatroskaPad *collect_pad;
1825 GstPad *newpad = NULL;
1827 GstPadSetCapsFunction setcapsfunc = NULL;
1828 GstMatroskaTrackContext *context = NULL;
1830 if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
1831 name = g_strdup_printf ("audio_%d", mux->num_a_streams++);
1832 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_audio_pad_setcaps);
1833 context = (GstMatroskaTrackContext *)
1834 g_new0 (GstMatroskaTrackAudioContext, 1);
1835 context->type = GST_MATROSKA_TRACK_TYPE_AUDIO;
1836 context->name = g_strdup ("Audio");
1837 } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
1838 name = g_strdup_printf ("video_%d", mux->num_v_streams++);
1839 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_video_pad_setcaps);
1840 context = (GstMatroskaTrackContext *)
1841 g_new0 (GstMatroskaTrackVideoContext, 1);
1842 context->type = GST_MATROSKA_TRACK_TYPE_VIDEO;
1843 context->name = g_strdup ("Video");
1844 } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%d")) {
1845 name = g_strdup_printf ("subtitle_%d", mux->num_t_streams++);
1846 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_subtitle_pad_setcaps);
1847 context = (GstMatroskaTrackContext *)
1848 g_new0 (GstMatroskaTrackSubtitleContext, 1);
1849 context->type = GST_MATROSKA_TRACK_TYPE_SUBTITLE;
1850 context->name = g_strdup ("Subtitle");
1852 GST_WARNING_OBJECT (mux, "This is not our template!");
1856 newpad = gst_pad_new_from_template (templ, name);
1858 collect_pad = (GstMatroskaPad *)
1859 gst_collect_pads_add_pad_full (mux->collect, newpad,
1860 sizeof (GstMatroskaPad),
1861 (GstCollectDataDestroyNotify) gst_matroska_pad_free);
1863 collect_pad->track = context;
1864 gst_matroska_pad_reset (collect_pad, FALSE);
1866 /* FIXME: hacked way to override/extend the event function of
1867 * GstCollectPads; because it sets its own event function giving the
1868 * element no access to events.
1869 * TODO GstCollectPads should really give its 'users' a clean chance to
1870 * properly handle events that are not meant for collectpads itself.
1871 * Perhaps a callback or so, though rejected (?) in #340060.
1872 * This would allow (clean) transcoding of info from demuxer/streams
1873 * to another muxer */
1874 mux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
1875 gst_pad_set_event_function (newpad,
1876 GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_sink_event));
1878 gst_pad_set_setcaps_function (newpad, setcapsfunc);
1879 gst_pad_set_active (newpad, TRUE);
1880 gst_element_add_pad (element, newpad);
1887 * gst_matroska_mux_release_pad:
1888 * @element: #GstMatroskaMux.
1889 * @pad: Pad to release.
1891 * Release a previously requested pad.
1894 gst_matroska_mux_release_pad (GstElement * element, GstPad * pad)
1896 GstMatroskaMux *mux;
1899 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1901 for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
1902 GstCollectData *cdata = (GstCollectData *) walk->data;
1903 GstMatroskaPad *collect_pad = (GstMatroskaPad *) cdata;
1905 if (cdata->pad == pad) {
1906 GstClockTime min_dur; /* observed minimum duration */
1908 if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) &&
1909 GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) {
1910 min_dur = GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts);
1911 if (collect_pad->duration < min_dur)
1912 collect_pad->duration = min_dur;
1915 if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
1916 mux->duration < collect_pad->duration)
1917 mux->duration = collect_pad->duration;
1923 gst_collect_pads_remove_pad (mux->collect, pad);
1924 if (gst_element_remove_pad (element, pad))
1930 * gst_matroska_mux_track_header:
1931 * @mux: #GstMatroskaMux
1932 * @context: Tack context.
1934 * Write a track header.
1937 gst_matroska_mux_track_header (GstMatroskaMux * mux,
1938 GstMatroskaTrackContext * context)
1940 GstEbmlWrite *ebml = mux->ebml_write;
1943 /* TODO: check if everything necessary is written and check default values */
1945 /* track type goes before the type-specific stuff */
1946 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKNUMBER, context->num);
1947 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKTYPE, context->type);
1949 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKUID,
1950 gst_matroska_mux_create_uid ());
1951 if (context->default_duration) {
1952 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKDEFAULTDURATION,
1953 context->default_duration);
1955 if (context->language) {
1956 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKLANGUAGE,
1960 /* type-specific stuff */
1961 switch (context->type) {
1962 case GST_MATROSKA_TRACK_TYPE_VIDEO:{
1963 GstMatroskaTrackVideoContext *videocontext =
1964 (GstMatroskaTrackVideoContext *) context;
1966 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKVIDEO);
1967 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELWIDTH,
1968 videocontext->pixel_width);
1969 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELHEIGHT,
1970 videocontext->pixel_height);
1971 if (videocontext->display_width && videocontext->display_height) {
1972 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYWIDTH,
1973 videocontext->display_width);
1974 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYHEIGHT,
1975 videocontext->display_height);
1977 if (context->flags & GST_MATROSKA_VIDEOTRACK_INTERLACED)
1978 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOFLAGINTERLACED, 1);
1979 if (videocontext->fourcc) {
1980 guint32 fcc_le = GUINT32_TO_LE (videocontext->fourcc);
1982 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_VIDEOCOLOURSPACE,
1983 (gpointer) & fcc_le, 4);
1985 gst_ebml_write_master_finish (ebml, master);
1990 case GST_MATROSKA_TRACK_TYPE_AUDIO:{
1991 GstMatroskaTrackAudioContext *audiocontext =
1992 (GstMatroskaTrackAudioContext *) context;
1994 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKAUDIO);
1995 if (audiocontext->samplerate != 8000)
1996 gst_ebml_write_float (ebml, GST_MATROSKA_ID_AUDIOSAMPLINGFREQ,
1997 audiocontext->samplerate);
1998 if (audiocontext->channels != 1)
1999 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOCHANNELS,
2000 audiocontext->channels);
2001 if (audiocontext->bitdepth) {
2002 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOBITDEPTH,
2003 audiocontext->bitdepth);
2005 gst_ebml_write_master_finish (ebml, master);
2011 /* doesn't need type-specific data */
2015 gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CODECID, context->codec_id);
2016 if (context->codec_priv)
2017 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECPRIVATE,
2018 context->codec_priv, context->codec_priv_size);
2019 /* FIXME: until we have a nice way of getting the codecname
2020 * out of the caps, I'm not going to enable this. Too much
2021 * (useless, double, boring) work... */
2022 /* TODO: Use value from tags if any */
2023 /*gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CODECNAME,
2024 context->codec_name); */
2025 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKNAME, context->name);
2030 * gst_matroska_mux_start:
2031 * @mux: #GstMatroskaMux
2033 * Start a new matroska file (write headers etc...)
2036 gst_matroska_mux_start (GstMatroskaMux * mux)
2038 GstEbmlWrite *ebml = mux->ebml_write;
2039 guint32 seekhead_id[] = { GST_MATROSKA_ID_SEGMENTINFO,
2040 GST_MATROSKA_ID_TRACKS,
2041 GST_MATROSKA_ID_CUES,
2042 GST_MATROSKA_ID_TAGS,
2045 guint64 master, child;
2049 GstClockTime duration = 0;
2050 guint32 segment_uid[4];
2051 GTimeVal time = { 0, 0 };
2053 /* we start with a EBML header */
2054 gst_ebml_write_header (ebml, "matroska", mux->matroska_version);
2056 /* start a segment */
2058 gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENT);
2059 mux->segment_master = ebml->pos;
2061 /* the rest of the header is cached */
2062 gst_ebml_write_set_cache (ebml, 0x1000);
2064 /* seekhead (table of contents) - we set the positions later */
2065 mux->seekhead_pos = ebml->pos;
2066 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD);
2067 for (i = 0; seekhead_id[i] != 0; i++) {
2068 child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKENTRY);
2069 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, seekhead_id[i]);
2070 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION, -1);
2071 gst_ebml_write_master_finish (ebml, child);
2073 gst_ebml_write_master_finish (ebml, master);
2076 mux->info_pos = ebml->pos;
2077 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENTINFO);
2078 for (i = 0; i < 4; i++) {
2079 segment_uid[i] = g_random_int ();
2081 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_SEGMENTUID,
2082 (guint8 *) segment_uid, 16);
2083 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TIMECODESCALE, mux->time_scale);
2084 mux->duration_pos = ebml->pos;
2086 for (collected = mux->collect->data; collected;
2087 collected = g_slist_next (collected)) {
2088 GstMatroskaPad *collect_pad;
2089 GstFormat format = GST_FORMAT_TIME;
2091 gint64 trackduration;
2093 collect_pad = (GstMatroskaPad *) collected->data;
2094 thepad = collect_pad->collect.pad;
2096 /* Query the total length of the track. */
2097 GST_DEBUG_OBJECT (thepad, "querying peer duration");
2098 if (gst_pad_query_peer_duration (thepad, &format, &trackduration)) {
2099 GST_DEBUG_OBJECT (thepad, "duration: %" GST_TIME_FORMAT,
2100 GST_TIME_ARGS (trackduration));
2101 if (trackduration != GST_CLOCK_TIME_NONE && trackduration > duration) {
2102 duration = (GstClockTime) trackduration;
2106 gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
2107 gst_guint64_to_gdouble (duration) /
2108 gst_guint64_to_gdouble (mux->time_scale));
2110 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_MUXINGAPP,
2111 "GStreamer plugin version " PACKAGE_VERSION);
2112 if (mux->writing_app && mux->writing_app[0]) {
2113 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_WRITINGAPP, mux->writing_app);
2115 g_get_current_time (&time);
2116 gst_ebml_write_date (ebml, GST_MATROSKA_ID_DATEUTC, time.tv_sec);
2117 gst_ebml_write_master_finish (ebml, master);
2120 mux->tracks_pos = ebml->pos;
2121 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKS);
2123 for (collected = mux->collect->data; collected;
2124 collected = g_slist_next (collected)) {
2125 GstMatroskaPad *collect_pad;
2128 collect_pad = (GstMatroskaPad *) collected->data;
2129 thepad = collect_pad->collect.pad;
2131 if (gst_pad_is_linked (thepad) && gst_pad_is_active (thepad) &&
2132 collect_pad->track->codec_id != 0) {
2133 collect_pad->track->num = tracknum++;
2134 child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKENTRY);
2135 gst_matroska_mux_track_header (mux, collect_pad->track);
2136 gst_ebml_write_master_finish (ebml, child);
2139 gst_ebml_write_master_finish (ebml, master);
2141 /* lastly, flush the cache */
2142 gst_ebml_write_flush_cache (ebml);
2146 gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag,
2149 /* TODO: more sensible tag mappings */
2152 gchar *matroska_tagname;
2153 gchar *gstreamer_tagname;
2157 GST_MATROSKA_TAG_ID_TITLE, GST_TAG_TITLE}, {
2158 GST_MATROSKA_TAG_ID_AUTHOR, GST_TAG_ARTIST}, {
2159 GST_MATROSKA_TAG_ID_ALBUM, GST_TAG_ALBUM}, {
2160 GST_MATROSKA_TAG_ID_COMMENTS, GST_TAG_COMMENT}, {
2161 GST_MATROSKA_TAG_ID_BITSPS, GST_TAG_BITRATE}, {
2162 GST_MATROSKA_TAG_ID_BPS, GST_TAG_BITRATE}, {
2163 GST_MATROSKA_TAG_ID_ENCODER, GST_TAG_ENCODER}, {
2164 GST_MATROSKA_TAG_ID_DATE, GST_TAG_DATE}, {
2165 GST_MATROSKA_TAG_ID_ISRC, GST_TAG_ISRC}, {
2166 GST_MATROSKA_TAG_ID_COPYRIGHT, GST_TAG_COPYRIGHT}, {
2167 GST_MATROSKA_TAG_ID_BPM, GST_TAG_BEATS_PER_MINUTE}, {
2168 GST_MATROSKA_TAG_ID_TERMS_OF_USE, GST_TAG_LICENSE}, {
2169 GST_MATROSKA_TAG_ID_COMPOSER, GST_TAG_COMPOSER}, {
2170 GST_MATROSKA_TAG_ID_LEAD_PERFORMER, GST_TAG_PERFORMER}, {
2171 GST_MATROSKA_TAG_ID_GENRE, GST_TAG_GENRE}
2173 GstEbmlWrite *ebml = (GstEbmlWrite *) data;
2175 guint64 simpletag_master;
2177 for (i = 0; i < G_N_ELEMENTS (tag_conv); i++) {
2178 const gchar *tagname_gst = tag_conv[i].gstreamer_tagname;
2179 const gchar *tagname_mkv = tag_conv[i].matroska_tagname;
2181 if (strcmp (tagname_gst, tag) == 0) {
2182 GValue src = { 0, };
2185 if (!gst_tag_list_copy_value (&src, list, tag))
2187 if ((dest = gst_value_serialize (&src))) {
2189 simpletag_master = gst_ebml_write_master_start (ebml,
2190 GST_MATROSKA_ID_SIMPLETAG);
2191 gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_TAGNAME, tagname_mkv);
2192 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TAGSTRING, dest);
2193 gst_ebml_write_master_finish (ebml, simpletag_master);
2196 GST_WARNING ("Can't transform tag '%s' to string", tagname_mkv);
2198 g_value_unset (&src);
2206 * gst_matroska_mux_finish:
2207 * @mux: #GstMatroskaMux
2209 * Finish a new matroska file (write index etc...)
2212 gst_matroska_mux_finish (GstMatroskaMux * mux)
2214 GstEbmlWrite *ebml = mux->ebml_write;
2216 guint64 duration = 0;
2218 const GstTagList *tags;
2220 /* finish last cluster */
2222 gst_ebml_write_master_finish (ebml, mux->cluster);
2226 if (mux->index != NULL) {
2228 guint64 master, pointentry_master, trackpos_master;
2230 mux->cues_pos = ebml->pos;
2231 gst_ebml_write_set_cache (ebml, 12 + 41 * mux->num_indexes);
2232 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CUES);
2234 for (n = 0; n < mux->num_indexes; n++) {
2235 GstMatroskaIndex *idx = &mux->index[n];
2237 pointentry_master = gst_ebml_write_master_start (ebml,
2238 GST_MATROSKA_ID_POINTENTRY);
2239 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETIME,
2240 idx->time / mux->time_scale);
2241 trackpos_master = gst_ebml_write_master_start (ebml,
2242 GST_MATROSKA_ID_CUETRACKPOSITIONS);
2243 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETRACK, idx->track);
2244 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUECLUSTERPOSITION,
2245 idx->pos - mux->segment_master);
2246 gst_ebml_write_master_finish (ebml, trackpos_master);
2247 gst_ebml_write_master_finish (ebml, pointentry_master);
2250 gst_ebml_write_master_finish (ebml, master);
2251 gst_ebml_write_flush_cache (ebml);
2255 tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (mux));
2257 if (tags != NULL && !gst_tag_list_is_empty (tags)) {
2258 guint64 master_tags, master_tag;
2260 GST_DEBUG ("Writing tags");
2262 /* TODO: maybe limit via the TARGETS id by looking at the source pad */
2263 mux->tags_pos = ebml->pos;
2264 master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS);
2265 master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG);
2266 gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml);
2267 gst_ebml_write_master_finish (ebml, master_tag);
2268 gst_ebml_write_master_finish (ebml, master_tags);
2271 /* update seekhead. We know that:
2272 * - a seekhead contains 4 entries.
2273 * - order of entries is as above.
2274 * - a seekhead has a 4-byte header + 8-byte length
2275 * - each entry is 2-byte master, 2-byte ID pointer,
2276 * 2-byte length pointer, all 8/1-byte length, 4-
2277 * byte ID and 8-byte length pointer, where the
2278 * length pointer starts at 20.
2279 * - all entries are local to the segment (so pos - segment_master).
2280 * - so each entry is at 12 + 20 + num * 28. */
2281 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 32,
2282 mux->info_pos - mux->segment_master);
2283 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 60,
2284 mux->tracks_pos - mux->segment_master);
2285 if (mux->index != NULL) {
2286 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88,
2287 mux->cues_pos - mux->segment_master);
2290 guint64 my_pos = ebml->pos;
2292 gst_ebml_write_seek (ebml, mux->seekhead_pos + 68);
2293 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
2294 gst_ebml_write_seek (ebml, my_pos);
2297 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116,
2298 mux->tags_pos - mux->segment_master);
2301 guint64 my_pos = ebml->pos;
2303 gst_ebml_write_seek (ebml, mux->seekhead_pos + 96);
2304 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
2305 gst_ebml_write_seek (ebml, my_pos);
2308 /* update duration */
2309 /* first get the overall duration */
2310 /* a released track may have left a duration in here */
2311 duration = mux->duration;
2312 for (collected = mux->collect->data; collected;
2313 collected = g_slist_next (collected)) {
2314 GstMatroskaPad *collect_pad;
2315 GstClockTime min_duration; /* observed minimum duration */
2317 collect_pad = (GstMatroskaPad *) collected->data;
2319 GST_DEBUG_OBJECT (mux, "Pad %" GST_PTR_FORMAT " start ts %" GST_TIME_FORMAT
2320 " end ts %" GST_TIME_FORMAT, collect_pad,
2321 GST_TIME_ARGS (collect_pad->start_ts),
2322 GST_TIME_ARGS (collect_pad->end_ts));
2324 if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) &&
2325 GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) {
2327 GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts);
2328 if (collect_pad->duration < min_duration)
2329 collect_pad->duration = min_duration;
2330 GST_DEBUG_OBJECT (collect_pad, "final track duration: %" GST_TIME_FORMAT,
2331 GST_TIME_ARGS (collect_pad->duration));
2334 if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
2335 duration < collect_pad->duration)
2336 duration = collect_pad->duration;
2338 if (duration != 0) {
2339 GST_DEBUG_OBJECT (mux, "final total duration: %" GST_TIME_FORMAT,
2340 GST_TIME_ARGS (duration));
2341 pos = mux->ebml_write->pos;
2342 gst_ebml_write_seek (ebml, mux->duration_pos);
2343 gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
2344 gst_guint64_to_gdouble (duration) /
2345 gst_guint64_to_gdouble (mux->time_scale));
2346 gst_ebml_write_seek (ebml, pos);
2349 guint64 my_pos = ebml->pos;
2351 gst_ebml_write_seek (ebml, mux->duration_pos);
2352 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 8);
2353 gst_ebml_write_seek (ebml, my_pos);
2356 /* finish segment - this also writes element length */
2357 gst_ebml_write_master_finish (ebml, mux->segment_pos);
2362 * gst_matroska_mux_best_pad:
2363 * @mux: #GstMatroskaMux
2364 * @popped: True if at least one buffer was popped from #GstCollectPads
2366 * Find a pad with the oldest data
2367 * (data from this pad should be written first).
2369 * Returns: Selected pad.
2371 static GstMatroskaPad *
2372 gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped)
2375 GstMatroskaPad *best = NULL;
2378 for (collected = mux->collect->data; collected;
2379 collected = g_slist_next (collected)) {
2380 GstMatroskaPad *collect_pad;
2382 collect_pad = (GstMatroskaPad *) collected->data;
2383 /* fetch a new buffer if needed */
2384 if (collect_pad->buffer == NULL) {
2385 collect_pad->buffer = gst_collect_pads_pop (mux->collect,
2386 (GstCollectData *) collect_pad);
2388 if (collect_pad->buffer != NULL)
2392 /* if we have a buffer check if it is better then the current best one */
2393 if (collect_pad->buffer != NULL) {
2394 if (best == NULL || !GST_BUFFER_TIMESTAMP_IS_VALID (collect_pad->buffer)
2395 || (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)
2396 && GST_BUFFER_TIMESTAMP (collect_pad->buffer) <
2397 GST_BUFFER_TIMESTAMP (best->buffer))) {
2407 * gst_matroska_mux_buffer_header:
2408 * @track: Track context.
2409 * @relative_timestamp: relative timestamp of the buffer
2410 * @flags: Buffer flags.
2412 * Create a buffer containing buffer header.
2414 * Returns: New buffer.
2417 gst_matroska_mux_create_buffer_header (GstMatroskaTrackContext * track,
2418 gint16 relative_timestamp, int flags)
2422 hdr = gst_buffer_new_and_alloc (4);
2423 /* track num - FIXME: what if num >= 0x80 (unlikely)? */
2424 GST_BUFFER_DATA (hdr)[0] = track->num | 0x80;
2425 /* time relative to clustertime */
2426 GST_WRITE_UINT16_BE (GST_BUFFER_DATA (hdr) + 1, relative_timestamp);
2429 GST_BUFFER_DATA (hdr)[3] = flags;
2435 gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux,
2436 GstMatroskaPad * collect_pad, GstBuffer * buf)
2438 GstMatroskaTrackVideoContext *ctx =
2439 (GstMatroskaTrackVideoContext *) collect_pad->track;
2440 const guint8 *data = GST_BUFFER_DATA (buf);
2441 guint size = GST_BUFFER_SIZE (buf);
2443 guint32 next_parse_offset;
2444 GstBuffer *ret = NULL;
2445 gboolean is_picture = FALSE;
2447 if (GST_BUFFER_SIZE (buf) < 13) {
2448 gst_buffer_unref (buf);
2452 /* Check if this buffer contains a picture packet */
2453 while (size >= 13) {
2454 if (GST_READ_UINT32_BE (data) != 0x42424344) {
2455 gst_buffer_unref (buf);
2459 parse_code = GST_READ_UINT8 (data + 4);
2460 if (parse_code == 0x00) {
2461 if (ctx->dirac_unit) {
2462 gst_buffer_unref (ctx->dirac_unit);
2463 ctx->dirac_unit = NULL;
2465 } else if (parse_code & 0x08) {
2470 next_parse_offset = GST_READ_UINT32_BE (data + 5);
2472 data += next_parse_offset;
2473 size -= next_parse_offset;
2476 if (ctx->dirac_unit)
2477 ctx->dirac_unit = gst_buffer_join (ctx->dirac_unit, gst_buffer_ref (buf));
2479 ctx->dirac_unit = gst_buffer_ref (buf);
2482 ret = gst_buffer_make_metadata_writable (ctx->dirac_unit);
2483 ctx->dirac_unit = NULL;
2484 gst_buffer_copy_metadata (ret, buf,
2485 GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS |
2486 GST_BUFFER_COPY_CAPS);
2487 gst_buffer_unref (buf);
2489 gst_buffer_unref (buf);
2497 * gst_matroska_mux_write_data:
2498 * @mux: #GstMatroskaMux
2499 * @collect_pad: #GstMatroskaPad with the data
2501 * Write collected data (called from gst_matroska_mux_collected).
2503 * Returns: Result of the gst_pad_push issued to write the data.
2505 static GstFlowReturn
2506 gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
2508 GstEbmlWrite *ebml = mux->ebml_write;
2509 GstBuffer *buf, *hdr;
2511 gboolean write_duration;
2512 gint16 relative_timestamp;
2513 gint64 relative_timestamp64;
2514 guint64 block_duration;
2515 gboolean is_video_keyframe = FALSE;
2518 buf = collect_pad->buffer;
2519 collect_pad->buffer = NULL;
2521 /* vorbis/theora headers are retrieved from caps and put in CodecPrivate */
2522 if (collect_pad->track->xiph_headers_to_skip > 0) {
2523 GST_LOG_OBJECT (collect_pad->collect.pad, "dropping streamheader buffer");
2524 gst_buffer_unref (buf);
2525 --collect_pad->track->xiph_headers_to_skip;
2529 /* for dirac we have to queue up everything up to a picture unit */
2530 if (collect_pad->track->codec_id != NULL &&
2531 strcmp (collect_pad->track->codec_id,
2532 GST_MATROSKA_CODEC_ID_VIDEO_DIRAC) == 0) {
2533 buf = gst_matroska_mux_handle_dirac_packet (mux, collect_pad, buf);
2538 /* hm, invalid timestamp (due to --to be fixed--- element upstream);
2539 * this would wreak havoc with time stored in matroska file */
2540 /* TODO: maybe calculate a timestamp by using the previous timestamp
2541 * and default duration */
2542 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
2543 GST_WARNING_OBJECT (collect_pad->collect.pad,
2544 "Invalid buffer timestamp; dropping buffer");
2545 gst_buffer_unref (buf);
2549 /* set the timestamp for outgoing buffers */
2550 ebml->timestamp = GST_BUFFER_TIMESTAMP (buf);
2552 if (collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_VIDEO &&
2553 !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
2554 GST_LOG_OBJECT (mux, "have video keyframe, ts=%" GST_TIME_FORMAT,
2555 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
2556 is_video_keyframe = TRUE;
2560 /* start a new cluster every two seconds or at keyframe */
2561 if (mux->cluster_time + GST_SECOND * 2 < GST_BUFFER_TIMESTAMP (buf)
2562 || is_video_keyframe) {
2564 gst_ebml_write_master_finish (ebml, mux->cluster);
2565 mux->cluster_pos = ebml->pos;
2567 gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
2568 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
2569 GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
2570 mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
2575 mux->cluster_pos = ebml->pos;
2576 mux->cluster = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
2577 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
2578 GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
2579 mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
2582 /* update duration of this track */
2583 if (GST_BUFFER_DURATION_IS_VALID (buf))
2584 collect_pad->duration += GST_BUFFER_DURATION (buf);
2586 /* We currently write an index entry for each keyframe in a
2587 * video track or one entry for each cluster in an audio track
2588 * for audio only files. This can be largely improved, such as doing
2589 * one for each keyframe or each second (for all-keyframe
2590 * streams), only the *first* video track. But that'll come later... */
2592 /* TODO: index is useful for every track, should contain the number of
2593 * the block in the cluster which contains the timestamp
2595 if (is_video_keyframe) {
2596 GstMatroskaIndex *idx;
2598 if (mux->num_indexes % 32 == 0) {
2599 mux->index = g_renew (GstMatroskaIndex, mux->index,
2600 mux->num_indexes + 32);
2602 idx = &mux->index[mux->num_indexes++];
2604 idx->pos = mux->cluster_pos;
2605 idx->time = GST_BUFFER_TIMESTAMP (buf);
2606 idx->track = collect_pad->track->num;
2607 } else if ((collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_AUDIO) &&
2608 (mux->num_streams == 1)) {
2609 GstMatroskaIndex *idx;
2611 if (mux->num_indexes % 32 == 0) {
2612 mux->index = g_renew (GstMatroskaIndex, mux->index,
2613 mux->num_indexes + 32);
2615 idx = &mux->index[mux->num_indexes++];
2617 idx->pos = mux->cluster_pos;
2618 idx->time = GST_BUFFER_TIMESTAMP (buf);
2619 idx->track = collect_pad->track->num;
2622 /* Check if the duration differs from the default duration. */
2623 write_duration = FALSE;
2624 block_duration = GST_BUFFER_DURATION (buf);
2625 if (GST_BUFFER_DURATION_IS_VALID (buf)) {
2626 if (block_duration != collect_pad->track->default_duration) {
2627 write_duration = TRUE;
2631 /* write the block, for matroska v2 use SimpleBlock if possible
2632 * one slice (*breath*).
2633 * FIXME: Need to do correct lacing! */
2634 relative_timestamp64 = GST_BUFFER_TIMESTAMP (buf) - mux->cluster_time;
2635 if (relative_timestamp64 >= 0) {
2636 /* round the timestamp */
2637 relative_timestamp64 += mux->time_scale / 2;
2639 /* round the timestamp */
2640 relative_timestamp64 -= mux->time_scale / 2;
2642 relative_timestamp = relative_timestamp64 / (gint64) mux->time_scale;
2643 if (mux->matroska_version > 1 && !write_duration) {
2645 GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) ? 0 : 0x80;
2648 gst_matroska_mux_create_buffer_header (collect_pad->track,
2649 relative_timestamp, flags);
2650 gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_SIMPLEBLOCK,
2651 GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
2652 gst_ebml_write_buffer (ebml, hdr);
2653 gst_ebml_write_buffer (ebml, buf);
2655 return gst_ebml_last_write_result (ebml);
2657 blockgroup = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_BLOCKGROUP);
2659 gst_matroska_mux_create_buffer_header (collect_pad->track,
2660 relative_timestamp, 0);
2661 gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_BLOCK,
2662 GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
2663 gst_ebml_write_buffer (ebml, hdr);
2664 gst_ebml_write_buffer (ebml, buf);
2665 if (write_duration) {
2666 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_BLOCKDURATION,
2667 block_duration / mux->time_scale);
2669 gst_ebml_write_master_finish (ebml, blockgroup);
2670 return gst_ebml_last_write_result (ebml);
2676 * gst_matroska_mux_collected:
2677 * @pads: #GstCollectPads
2678 * @uuser_data: #GstMatroskaMux
2680 * Collectpads callback.
2682 * Returns: #GstFlowReturn
2684 static GstFlowReturn
2685 gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data)
2687 GstMatroskaMux *mux = GST_MATROSKA_MUX (user_data);
2688 GstMatroskaPad *best;
2692 GST_DEBUG_OBJECT (mux, "Collected pads");
2694 /* start with a header */
2695 if (mux->state == GST_MATROSKA_MUX_STATE_START) {
2696 if (mux->collect->data == NULL) {
2697 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
2698 ("No input streams configured"));
2699 return GST_FLOW_ERROR;
2701 mux->state = GST_MATROSKA_MUX_STATE_HEADER;
2702 gst_matroska_mux_start (mux);
2703 mux->state = GST_MATROSKA_MUX_STATE_DATA;
2707 /* which stream to write from? */
2708 best = gst_matroska_mux_best_pad (mux, &popped);
2710 /* if there is no best pad, we have reached EOS */
2712 GST_DEBUG_OBJECT (mux, "No best pad finishing...");
2713 gst_matroska_mux_finish (mux);
2714 gst_pad_push_event (mux->srcpad, gst_event_new_eos ());
2715 ret = GST_FLOW_UNEXPECTED;
2718 GST_DEBUG_OBJECT (best->collect.pad, "best pad - buffer ts %"
2719 GST_TIME_FORMAT " dur %" GST_TIME_FORMAT,
2720 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (best->buffer)),
2721 GST_TIME_ARGS (GST_BUFFER_DURATION (best->buffer)));
2723 /* make note of first and last encountered timestamps, so we can calculate
2724 * the actual duration later when we send an updated header on eos */
2725 if (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)) {
2726 GstClockTime start_ts = GST_BUFFER_TIMESTAMP (best->buffer);
2727 GstClockTime end_ts = start_ts;
2729 if (GST_BUFFER_DURATION_IS_VALID (best->buffer))
2730 end_ts += GST_BUFFER_DURATION (best->buffer);
2731 else if (best->track->default_duration)
2732 end_ts += best->track->default_duration;
2734 if (!GST_CLOCK_TIME_IS_VALID (best->end_ts) || end_ts > best->end_ts)
2735 best->end_ts = end_ts;
2737 if (G_UNLIKELY (best->start_ts == GST_CLOCK_TIME_NONE ||
2738 start_ts < best->start_ts))
2739 best->start_ts = start_ts;
2742 /* write one buffer */
2743 ret = gst_matroska_mux_write_data (mux, best);
2744 } while (ret == GST_FLOW_OK && !popped);
2751 * gst_matroska_mux_change_state:
2752 * @element: #GstMatroskaMux
2753 * @transition: State change transition.
2755 * Change the muxer state.
2757 * Returns: #GstStateChangeReturn
2759 static GstStateChangeReturn
2760 gst_matroska_mux_change_state (GstElement * element, GstStateChange transition)
2762 GstStateChangeReturn ret;
2763 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
2765 switch (transition) {
2766 case GST_STATE_CHANGE_NULL_TO_READY:
2768 case GST_STATE_CHANGE_READY_TO_PAUSED:
2769 gst_collect_pads_start (mux->collect);
2771 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
2773 case GST_STATE_CHANGE_PAUSED_TO_READY:
2774 gst_collect_pads_stop (mux->collect);
2780 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2782 switch (transition) {
2783 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
2785 case GST_STATE_CHANGE_PAUSED_TO_READY:
2786 gst_matroska_mux_reset (GST_ELEMENT (mux));
2788 case GST_STATE_CHANGE_READY_TO_NULL:
2798 gst_matroska_mux_set_property (GObject * object,
2799 guint prop_id, const GValue * value, GParamSpec * pspec)
2801 GstMatroskaMux *mux;
2803 g_return_if_fail (GST_IS_MATROSKA_MUX (object));
2804 mux = GST_MATROSKA_MUX (object);
2807 case ARG_WRITING_APP:
2808 if (!g_value_get_string (value)) {
2809 GST_WARNING_OBJECT (mux, "writing-app property can not be NULL");
2812 g_free (mux->writing_app);
2813 mux->writing_app = g_value_dup_string (value);
2815 case ARG_MATROSKA_VERSION:
2816 mux->matroska_version = g_value_get_int (value);
2819 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2825 gst_matroska_mux_get_property (GObject * object,
2826 guint prop_id, GValue * value, GParamSpec * pspec)
2828 GstMatroskaMux *mux;
2830 g_return_if_fail (GST_IS_MATROSKA_MUX (object));
2831 mux = GST_MATROSKA_MUX (object);
2834 case ARG_WRITING_APP:
2835 g_value_set_string (value, mux->writing_app);
2837 case ARG_MATROSKA_VERSION:
2838 g_value_set_int (value, mux->matroska_version);
2841 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2847 gst_matroska_mux_plugin_init (GstPlugin * plugin)
2849 return gst_element_register (plugin, "matroskamux",
2850 GST_RANK_PRIMARY, GST_TYPE_MATROSKA_MUX);