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;
541 mux->prev_cluster_size = 0;
544 gst_tag_setter_reset_tags (GST_TAG_SETTER (mux));
548 * gst_matroska_mux_handle_src_event:
549 * @pad: Pad which received the event.
550 * @event: Received event.
552 * handle events - copied from oggmux without understanding
554 * Returns: #TRUE on success.
557 gst_matroska_mux_handle_src_event (GstPad * pad, GstEvent * event)
561 type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;
565 /* disable seeking for now */
571 return gst_pad_event_default (pad, event);
575 * gst_matroska_mux_handle_sink_event:
576 * @pad: Pad which received the event.
577 * @event: Received event.
579 * handle events - informational ones like tags
581 * Returns: #TRUE on success.
584 gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event)
586 GstMatroskaTrackContext *context;
587 GstMatroskaPad *collect_pad;
592 mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad));
594 /* FIXME: aren't we either leaking events here or doing a wrong unref? */
595 switch (GST_EVENT_TYPE (event)) {
599 GST_DEBUG_OBJECT (mux, "received tag event");
600 gst_event_parse_tag (event, &list);
602 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
603 g_assert (collect_pad);
604 context = collect_pad->track;
607 /* Matroska wants ISO 639-2B code, taglist most likely contains 639-1 */
608 if (gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &lang)) {
609 const gchar *lang_code;
611 lang_code = gst_tag_get_language_code_iso_639_2B (lang);
613 GST_INFO_OBJECT (pad, "Setting language to '%s'", lang_code);
614 context->language = g_strdup (lang_code);
616 GST_WARNING_OBJECT (pad, "Did not get language code for '%s'", lang);
621 gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), list,
622 gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (mux)));
625 case GST_EVENT_NEWSEGMENT:
626 /* We don't support NEWSEGMENT events */
628 gst_event_unref (event);
634 /* now GstCollectPads can take care of the rest, e.g. EOS */
636 ret = mux->collect_event (pad, event);
637 gst_object_unref (mux);
644 * gst_matroska_mux_video_pad_setcaps:
645 * @pad: Pad which got the caps.
648 * Setcaps function for video sink pad.
650 * Returns: #TRUE on success.
653 gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
655 GstMatroskaTrackContext *context = NULL;
656 GstMatroskaTrackVideoContext *videocontext;
658 GstMatroskaPad *collect_pad;
659 GstStructure *structure;
660 const gchar *mimetype;
661 const GValue *value = NULL;
662 const GstBuffer *codec_buf = NULL;
663 gint width, height, pixel_width, pixel_height;
665 gboolean interlaced = FALSE;
667 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
670 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
671 g_assert (collect_pad);
672 context = collect_pad->track;
674 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO);
675 videocontext = (GstMatroskaTrackVideoContext *) context;
677 /* gst -> matroska ID'ing */
678 structure = gst_caps_get_structure (caps, 0);
680 mimetype = gst_structure_get_name (structure);
682 if (gst_structure_get_boolean (structure, "interlaced", &interlaced)
684 context->flags |= GST_MATROSKA_VIDEOTRACK_INTERLACED;
686 if (!strcmp (mimetype, "video/x-theora")) {
687 /* we'll extract the details later from the theora identification header */
691 /* get general properties */
692 /* spec says it is mandatory */
693 if (!gst_structure_get_int (structure, "width", &width) ||
694 !gst_structure_get_int (structure, "height", &height))
697 videocontext->pixel_width = width;
698 videocontext->pixel_height = height;
699 if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d)
701 context->default_duration =
702 gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n);
703 GST_LOG_OBJECT (pad, "default duration = %" GST_TIME_FORMAT,
704 GST_TIME_ARGS (context->default_duration));
706 context->default_duration = 0;
708 if (gst_structure_get_fraction (structure, "pixel-aspect-ratio",
709 &pixel_width, &pixel_height)) {
710 if (pixel_width > pixel_height) {
711 videocontext->display_width = width * pixel_width / pixel_height;
712 videocontext->display_height = height;
713 } else if (pixel_width < pixel_height) {
714 videocontext->display_width = width;
715 videocontext->display_height = height * pixel_height / pixel_width;
717 videocontext->display_width = 0;
718 videocontext->display_height = 0;
721 videocontext->display_width = 0;
722 videocontext->display_height = 0;
727 videocontext->asr_mode = GST_MATROSKA_ASPECT_RATIO_MODE_FREE;
728 videocontext->fourcc = 0;
730 /* TODO: - check if we handle all codecs by the spec, i.e. codec private
731 * data and other settings
735 /* extract codec_data, may turn out needed */
736 value = gst_structure_get_value (structure, "codec_data");
738 codec_buf = gst_value_get_buffer (value);
741 if (!strcmp (mimetype, "video/x-raw-yuv")) {
742 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED);
743 gst_structure_get_fourcc (structure, "format", &videocontext->fourcc);
744 } else if (!strcmp (mimetype, "image/jpeg")) {
745 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
746 } else if (!strcmp (mimetype, "video/x-xvid") /* MS/VfW compatibility cases */
747 ||!strcmp (mimetype, "video/x-huffyuv")
748 || !strcmp (mimetype, "video/x-divx")
749 || !strcmp (mimetype, "video/x-dv")
750 || !strcmp (mimetype, "video/x-h263")
751 || !strcmp (mimetype, "video/x-msmpeg")
752 || !strcmp (mimetype, "video/x-wmv")) {
753 gst_riff_strf_vids *bih;
754 gint size = sizeof (gst_riff_strf_vids);
757 if (!strcmp (mimetype, "video/x-xvid"))
758 fourcc = GST_MAKE_FOURCC ('X', 'V', 'I', 'D');
759 else if (!strcmp (mimetype, "video/x-huffyuv"))
760 fourcc = GST_MAKE_FOURCC ('H', 'F', 'Y', 'U');
761 else if (!strcmp (mimetype, "video/x-dv"))
762 fourcc = GST_MAKE_FOURCC ('D', 'V', 'S', 'D');
763 else if (!strcmp (mimetype, "video/x-h263"))
764 fourcc = GST_MAKE_FOURCC ('H', '2', '6', '3');
765 else if (!strcmp (mimetype, "video/x-divx")) {
768 gst_structure_get_int (structure, "divxversion", &divxversion);
769 switch (divxversion) {
771 fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', '3');
774 fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', 'X');
777 fourcc = GST_MAKE_FOURCC ('D', 'X', '5', '0');
780 } else if (!strcmp (mimetype, "video/x-msmpeg")) {
783 gst_structure_get_int (structure, "msmpegversion", &msmpegversion);
784 switch (msmpegversion) {
786 fourcc = GST_MAKE_FOURCC ('M', 'P', 'G', '4');
789 fourcc = GST_MAKE_FOURCC ('M', 'P', '4', '2');
795 } else if (!strcmp (mimetype, "video/x-wmv")) {
798 if (gst_structure_get_fourcc (structure, "format", &format)) {
800 } else if (gst_structure_get_int (structure, "wmvversion", &wmvversion)) {
801 if (wmvversion == 2) {
802 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '2');
803 } else if (wmvversion == 1) {
804 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '1');
805 } else if (wmvversion == 3) {
806 fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '3');
814 bih = g_new0 (gst_riff_strf_vids, 1);
815 GST_WRITE_UINT32_LE (&bih->size, size);
816 GST_WRITE_UINT32_LE (&bih->width, videocontext->pixel_width);
817 GST_WRITE_UINT32_LE (&bih->height, videocontext->pixel_height);
818 GST_WRITE_UINT32_LE (&bih->compression, fourcc);
819 GST_WRITE_UINT16_LE (&bih->planes, (guint16) 1);
820 GST_WRITE_UINT16_LE (&bih->bit_cnt, (guint16) 24);
821 GST_WRITE_UINT32_LE (&bih->image_size, videocontext->pixel_width *
822 videocontext->pixel_height * 3);
824 /* process codec private/initialization data, if any */
826 size += GST_BUFFER_SIZE (codec_buf);
827 bih = g_realloc (bih, size);
828 GST_WRITE_UINT32_LE (&bih->size, size);
829 memcpy ((guint8 *) bih + sizeof (gst_riff_strf_vids),
830 GST_BUFFER_DATA (codec_buf), GST_BUFFER_SIZE (codec_buf));
833 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
834 context->codec_priv = (gpointer) bih;
835 context->codec_priv_size = size;
836 } else if (!strcmp (mimetype, "video/x-h264")) {
837 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC);
839 if (context->codec_priv != NULL) {
840 g_free (context->codec_priv);
841 context->codec_priv = NULL;
842 context->codec_priv_size = 0;
845 /* Create avcC header */
846 if (codec_buf != NULL) {
847 context->codec_priv_size = GST_BUFFER_SIZE (codec_buf);
848 context->codec_priv = g_malloc0 (context->codec_priv_size);
849 memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
850 context->codec_priv_size);
852 } else if (!strcmp (mimetype, "video/x-theora")) {
853 const GValue *streamheader;
855 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_THEORA);
857 if (context->codec_priv != NULL) {
858 g_free (context->codec_priv);
859 context->codec_priv = NULL;
860 context->codec_priv_size = 0;
863 streamheader = gst_structure_get_value (structure, "streamheader");
864 if (!theora_streamheader_to_codecdata (streamheader, context)) {
865 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
866 ("theora stream headers missing or malformed"));
869 } else if (!strcmp (mimetype, "video/x-dirac")) {
870 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_DIRAC);
871 } else if (!strcmp (mimetype, "video/mpeg")) {
874 gst_structure_get_int (structure, "mpegversion", &mpegversion);
875 switch (mpegversion) {
877 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG1);
880 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG2);
883 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
889 /* global headers may be in codec data */
890 if (codec_buf != NULL) {
891 context->codec_priv_size = GST_BUFFER_SIZE (codec_buf);
892 context->codec_priv = g_malloc0 (context->codec_priv_size);
893 memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
894 context->codec_priv_size);
896 } else if (!strcmp (mimetype, "video/x-msmpeg")) {
898 /* can only make it here if preceding case verified it was version 3 */
899 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3);
900 } else if (!strcmp (mimetype, "video/x-pn-realvideo")) {
902 const GValue *mdpr_data;
904 gst_structure_get_int (structure, "rmversion", &rmversion);
907 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1);
910 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2);
913 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3);
916 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4);
922 mdpr_data = gst_structure_get_value (structure, "mdpr_data");
923 if (mdpr_data != NULL) {
924 guint8 *priv_data = NULL;
925 guint priv_data_size = 0;
927 GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data);
929 priv_data_size = GST_BUFFER_SIZE (codec_data_buf);
930 priv_data = g_malloc0 (priv_data_size);
932 memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size);
934 context->codec_priv = priv_data;
935 context->codec_priv_size = priv_data_size;
944 GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT,
945 GST_PAD_NAME (pad), caps);
950 /* N > 0 to expect a particular number of headers, negative if the
951 number of headers is variable */
953 xiphN_streamheader_to_codecdata (const GValue * streamheader,
954 GstMatroskaTrackContext * context, GstBuffer ** p_buf0, int N)
956 GstBuffer **buf = NULL;
959 guint bufi, i, offset, priv_data_size;
961 if (streamheader == NULL)
962 goto no_stream_headers;
964 if (G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY)
967 bufarr = g_value_peek_pointer (streamheader);
968 if (bufarr->len <= 0 || bufarr->len > 255) /* at least one header, and count stored in a byte */
970 if (N > 0 && bufarr->len != N)
973 context->xiph_headers_to_skip = bufarr->len;
975 buf = (GstBuffer **) g_malloc0 (sizeof (GstBuffer *) * bufarr->len);
976 for (i = 0; i < bufarr->len; i++) {
977 GValue *bufval = &g_array_index (bufarr, GValue, i);
979 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
981 goto wrong_content_type;
984 buf[i] = g_value_peek_pointer (bufval);
988 if (bufarr->len > 0) {
989 for (i = 0; i < bufarr->len - 1; i++) {
990 priv_data_size += GST_BUFFER_SIZE (buf[i]) / 0xff + 1;
994 for (i = 0; i < bufarr->len; ++i) {
995 priv_data_size += GST_BUFFER_SIZE (buf[i]);
998 priv_data = g_malloc0 (priv_data_size);
1000 priv_data[0] = bufarr->len - 1;
1003 if (bufarr->len > 0) {
1004 for (bufi = 0; bufi < bufarr->len - 1; bufi++) {
1005 for (i = 0; i < GST_BUFFER_SIZE (buf[bufi]) / 0xff; ++i) {
1006 priv_data[offset++] = 0xff;
1008 priv_data[offset++] = GST_BUFFER_SIZE (buf[bufi]) % 0xff;
1012 for (i = 0; i < bufarr->len; ++i) {
1013 memcpy (priv_data + offset, GST_BUFFER_DATA (buf[i]),
1014 GST_BUFFER_SIZE (buf[i]));
1015 offset += GST_BUFFER_SIZE (buf[i]);
1018 context->codec_priv = priv_data;
1019 context->codec_priv_size = priv_data_size;
1022 *p_buf0 = gst_buffer_ref (buf[0]);
1031 GST_WARNING ("required streamheaders missing in sink caps!");
1036 GST_WARNING ("streamheaders are not a GST_TYPE_ARRAY, but a %s",
1037 G_VALUE_TYPE_NAME (streamheader));
1042 GST_WARNING ("got %u streamheaders, not %d as expected", bufarr->len, N);
1047 GST_WARNING ("streamheaders array does not contain GstBuffers");
1053 vorbis_streamheader_to_codecdata (const GValue * streamheader,
1054 GstMatroskaTrackContext * context)
1056 GstBuffer *buf0 = NULL;
1058 if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3))
1061 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 4) {
1062 GST_WARNING ("First vorbis header too small, ignoring");
1064 if (memcmp (GST_BUFFER_DATA (buf0) + 1, "vorbis", 6) == 0) {
1065 GstMatroskaTrackAudioContext *audiocontext;
1068 hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 4;
1069 audiocontext = (GstMatroskaTrackAudioContext *) context;
1070 audiocontext->channels = GST_READ_UINT8 (hdr);
1071 audiocontext->samplerate = GST_READ_UINT32_LE (hdr + 1);
1076 gst_buffer_unref (buf0);
1082 theora_streamheader_to_codecdata (const GValue * streamheader,
1083 GstMatroskaTrackContext * context)
1085 GstBuffer *buf0 = NULL;
1087 if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3))
1090 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 26) {
1091 GST_WARNING ("First theora header too small, ignoring");
1092 } else if (memcmp (GST_BUFFER_DATA (buf0), "\200theora\003\002", 9) != 0) {
1093 GST_WARNING ("First header not a theora identification header, ignoring");
1095 GstMatroskaTrackVideoContext *videocontext;
1096 guint fps_num, fps_denom, par_num, par_denom;
1099 hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 3 + 2 + 2;
1101 videocontext = (GstMatroskaTrackVideoContext *) context;
1102 videocontext->pixel_width = GST_READ_UINT32_BE (hdr) >> 8;
1103 videocontext->pixel_height = GST_READ_UINT32_BE (hdr + 3) >> 8;
1104 hdr += 3 + 3 + 1 + 1;
1105 fps_num = GST_READ_UINT32_BE (hdr);
1106 fps_denom = GST_READ_UINT32_BE (hdr + 4);
1107 context->default_duration = gst_util_uint64_scale_int (GST_SECOND,
1108 fps_denom, fps_num);
1110 par_num = GST_READ_UINT32_BE (hdr) >> 8;
1111 par_denom = GST_READ_UINT32_BE (hdr + 3) >> 8;
1112 if (par_num > 0 && par_num > 0) {
1113 if (par_num > par_denom) {
1114 videocontext->display_width =
1115 videocontext->pixel_width * par_num / par_denom;
1116 videocontext->display_height = videocontext->pixel_height;
1117 } else if (par_num < par_denom) {
1118 videocontext->display_width = videocontext->pixel_width;
1119 videocontext->display_height =
1120 videocontext->pixel_height * par_denom / par_num;
1122 videocontext->display_width = 0;
1123 videocontext->display_height = 0;
1126 videocontext->display_width = 0;
1127 videocontext->display_height = 0;
1133 gst_buffer_unref (buf0);
1139 kate_streamheader_to_codecdata (const GValue * streamheader,
1140 GstMatroskaTrackContext * context)
1142 GstBuffer *buf0 = NULL;
1144 if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, -1))
1147 if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 64) { /* Kate ID header is 64 bytes */
1148 GST_WARNING ("First kate header too small, ignoring");
1149 } else if (memcmp (GST_BUFFER_DATA (buf0), "\200kate\0\0\0", 8) != 0) {
1150 GST_WARNING ("First header not a kate identification header, ignoring");
1154 gst_buffer_unref (buf0);
1160 flac_streamheader_to_codecdata (const GValue * streamheader,
1161 GstMatroskaTrackContext * context)
1168 if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) {
1169 GST_WARNING ("No or invalid streamheader field in the caps");
1173 bufarr = g_value_peek_pointer (streamheader);
1174 if (bufarr->len < 2) {
1175 GST_WARNING ("Too few headers in streamheader field");
1179 context->xiph_headers_to_skip = bufarr->len + 1;
1181 bufval = &g_array_index (bufarr, GValue, 0);
1182 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1183 GST_WARNING ("streamheaders array does not contain GstBuffers");
1187 buffer = g_value_peek_pointer (bufval);
1189 /* Need at least OggFLAC mapping header, fLaC marker and STREAMINFO block */
1190 if (GST_BUFFER_SIZE (buffer) < 9 + 4 + 4 + 34
1191 || memcmp (GST_BUFFER_DATA (buffer) + 1, "FLAC", 4) != 0
1192 || memcmp (GST_BUFFER_DATA (buffer) + 9, "fLaC", 4) != 0) {
1193 GST_WARNING ("Invalid streamheader for FLAC");
1197 context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer) - 9);
1198 context->codec_priv_size = GST_BUFFER_SIZE (buffer) - 9;
1199 memcpy (context->codec_priv, GST_BUFFER_DATA (buffer) + 9,
1200 GST_BUFFER_SIZE (buffer) - 9);
1202 for (i = 1; i < bufarr->len; i++) {
1203 bufval = &g_array_index (bufarr, GValue, i);
1205 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1206 g_free (context->codec_priv);
1207 context->codec_priv = NULL;
1208 context->codec_priv_size = 0;
1209 GST_WARNING ("streamheaders array does not contain GstBuffers");
1213 buffer = g_value_peek_pointer (bufval);
1215 context->codec_priv =
1216 g_realloc (context->codec_priv,
1217 context->codec_priv_size + GST_BUFFER_SIZE (buffer));
1218 memcpy ((guint8 *) context->codec_priv + context->codec_priv_size,
1219 GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
1220 context->codec_priv_size =
1221 context->codec_priv_size + GST_BUFFER_SIZE (buffer);
1228 speex_streamheader_to_codecdata (const GValue * streamheader,
1229 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 if (GST_BUFFER_SIZE (buffer) < 80
1257 || memcmp (GST_BUFFER_DATA (buffer), "Speex ", 8) != 0) {
1258 GST_WARNING ("Invalid streamheader for Speex");
1262 context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer));
1263 context->codec_priv_size = GST_BUFFER_SIZE (buffer);
1264 memcpy (context->codec_priv, GST_BUFFER_DATA (buffer),
1265 GST_BUFFER_SIZE (buffer));
1267 bufval = &g_array_index (bufarr, GValue, 1);
1269 if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
1270 g_free (context->codec_priv);
1271 context->codec_priv = NULL;
1272 context->codec_priv_size = 0;
1273 GST_WARNING ("streamheaders array does not contain GstBuffers");
1277 buffer = g_value_peek_pointer (bufval);
1279 context->codec_priv =
1280 g_realloc (context->codec_priv,
1281 context->codec_priv_size + GST_BUFFER_SIZE (buffer));
1282 memcpy ((guint8 *) context->codec_priv + context->codec_priv_size,
1283 GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
1284 context->codec_priv_size =
1285 context->codec_priv_size + GST_BUFFER_SIZE (buffer);
1290 static const gchar *
1291 aac_codec_data_to_codec_id (const GstBuffer * buf)
1293 const gchar *result;
1296 /* default to MAIN */
1299 if (GST_BUFFER_SIZE (buf) >= 2) {
1300 profile = GST_READ_UINT8 (GST_BUFFER_DATA (buf));
1318 GST_WARNING ("unknown AAC profile, defaulting to MAIN");
1327 * gst_matroska_mux_audio_pad_setcaps:
1328 * @pad: Pad which got the caps.
1331 * Setcaps function for audio sink pad.
1333 * Returns: #TRUE on success.
1336 gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
1338 GstMatroskaTrackContext *context = NULL;
1339 GstMatroskaTrackAudioContext *audiocontext;
1340 GstMatroskaMux *mux;
1341 GstMatroskaPad *collect_pad;
1342 const gchar *mimetype;
1343 gint samplerate = 0, channels = 0;
1344 GstStructure *structure;
1345 const GValue *codec_data = NULL;
1346 const GstBuffer *buf = NULL;
1347 const gchar *stream_format = NULL;
1349 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1352 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
1353 g_assert (collect_pad);
1354 context = collect_pad->track;
1356 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_AUDIO);
1357 audiocontext = (GstMatroskaTrackAudioContext *) context;
1359 structure = gst_caps_get_structure (caps, 0);
1360 mimetype = gst_structure_get_name (structure);
1363 gst_structure_get_int (structure, "rate", &samplerate);
1364 gst_structure_get_int (structure, "channels", &channels);
1366 audiocontext->samplerate = samplerate;
1367 audiocontext->channels = channels;
1368 audiocontext->bitdepth = 0;
1369 context->default_duration = 0;
1371 codec_data = gst_structure_get_value (structure, "codec_data");
1373 buf = gst_value_get_buffer (codec_data);
1375 /* TODO: - check if we handle all codecs by the spec, i.e. codec private
1376 * data and other settings
1380 if (!strcmp (mimetype, "audio/mpeg")) {
1381 gint mpegversion = 0;
1383 gst_structure_get_int (structure, "mpegversion", &mpegversion);
1384 switch (mpegversion) {
1390 gst_structure_get_int (structure, "layer", &layer);
1392 if (!gst_structure_get_int (structure, "mpegaudioversion", &version)) {
1393 GST_WARNING_OBJECT (mux,
1394 "Unable to determine MPEG audio version, assuming 1");
1400 else if (layer == 2)
1402 else if (version == 2)
1407 context->default_duration =
1408 gst_util_uint64_scale (GST_SECOND, spf, audiocontext->samplerate);
1412 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1);
1415 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2);
1418 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3);
1427 stream_format = gst_structure_get_string (structure, "stream-format");
1428 /* check this is raw aac */
1429 if (stream_format) {
1430 if (strcmp (stream_format, "raw") != 0) {
1431 GST_WARNING_OBJECT (mux, "AAC stream-format must be 'raw', not %s",
1435 GST_WARNING_OBJECT (mux, "AAC stream-format not specified, "
1440 if (mpegversion == 2)
1442 g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG2 "%s",
1443 aac_codec_data_to_codec_id (buf));
1444 else if (mpegversion == 4)
1446 g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4 "%s",
1447 aac_codec_data_to_codec_id (buf));
1449 g_assert_not_reached ();
1451 GST_DEBUG_OBJECT (mux, "no AAC codec_data; not packetized");
1458 } else if (!strcmp (mimetype, "audio/x-raw-int")) {
1460 gint endianness = G_LITTLE_ENDIAN;
1461 gboolean signedness = TRUE;
1463 if (!gst_structure_get_int (structure, "width", &width) ||
1464 !gst_structure_get_int (structure, "depth", &depth) ||
1465 !gst_structure_get_boolean (structure, "signed", &signedness)) {
1466 GST_DEBUG_OBJECT (mux, "broken caps, width/depth/signed field missing");
1471 !gst_structure_get_int (structure, "endianness", &endianness)) {
1472 GST_DEBUG_OBJECT (mux, "broken caps, no endianness specified");
1476 if (width != depth) {
1477 GST_DEBUG_OBJECT (mux, "width must be same as depth!");
1481 /* FIXME: where is this spec'ed out? (tpm) */
1482 if ((width == 8 && signedness) || (width >= 16 && !signedness)) {
1483 GST_DEBUG_OBJECT (mux, "8-bit PCM must be unsigned, 16-bit PCM signed");
1487 audiocontext->bitdepth = depth;
1488 if (endianness == G_BIG_ENDIAN)
1489 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE);
1491 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE);
1493 } else if (!strcmp (mimetype, "audio/x-raw-float")) {
1496 if (!gst_structure_get_int (structure, "width", &width)) {
1497 GST_DEBUG_OBJECT (mux, "broken caps, width field missing");
1501 audiocontext->bitdepth = width;
1502 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT);
1504 } else if (!strcmp (mimetype, "audio/x-vorbis")) {
1505 const GValue *streamheader;
1507 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_VORBIS);
1509 if (context->codec_priv != NULL) {
1510 g_free (context->codec_priv);
1511 context->codec_priv = NULL;
1512 context->codec_priv_size = 0;
1515 streamheader = gst_structure_get_value (structure, "streamheader");
1516 if (!vorbis_streamheader_to_codecdata (streamheader, context)) {
1517 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1518 ("vorbis stream headers missing or malformed"));
1521 } else if (!strcmp (mimetype, "audio/x-flac")) {
1522 const GValue *streamheader;
1524 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_FLAC);
1525 if (context->codec_priv != NULL) {
1526 g_free (context->codec_priv);
1527 context->codec_priv = NULL;
1528 context->codec_priv_size = 0;
1531 streamheader = gst_structure_get_value (structure, "streamheader");
1532 if (!flac_streamheader_to_codecdata (streamheader, context)) {
1533 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1534 ("flac stream headers missing or malformed"));
1537 } else if (!strcmp (mimetype, "audio/x-speex")) {
1538 const GValue *streamheader;
1540 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_SPEEX);
1541 if (context->codec_priv != NULL) {
1542 g_free (context->codec_priv);
1543 context->codec_priv = NULL;
1544 context->codec_priv_size = 0;
1547 streamheader = gst_structure_get_value (structure, "streamheader");
1548 if (!speex_streamheader_to_codecdata (streamheader, context)) {
1549 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1550 ("speex stream headers missing or malformed"));
1553 } else if (!strcmp (mimetype, "audio/x-ac3")) {
1554 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3);
1555 } else if (!strcmp (mimetype, "audio/x-tta")) {
1558 /* TTA frame duration */
1559 context->default_duration = 1.04489795918367346939 * GST_SECOND;
1561 gst_structure_get_int (structure, "width", &width);
1562 audiocontext->bitdepth = width;
1563 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_TTA);
1565 } else if (!strcmp (mimetype, "audio/x-pn-realaudio")) {
1567 const GValue *mdpr_data;
1569 gst_structure_get_int (structure, "raversion", &raversion);
1570 switch (raversion) {
1572 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4);
1575 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_28_8);
1578 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK);
1584 mdpr_data = gst_structure_get_value (structure, "mdpr_data");
1585 if (mdpr_data != NULL) {
1586 guint8 *priv_data = NULL;
1587 guint priv_data_size = 0;
1589 GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data);
1591 priv_data_size = GST_BUFFER_SIZE (codec_data_buf);
1592 priv_data = g_malloc0 (priv_data_size);
1594 memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size);
1596 context->codec_priv = priv_data;
1597 context->codec_priv_size = priv_data_size;
1600 } else if (!strcmp (mimetype, "audio/x-wma")) {
1602 guint codec_priv_size;
1609 if (!gst_structure_get_int (structure, "wmaversion", &wmaversion)
1610 || !gst_structure_get_int (structure, "block_align", &block_align)
1611 || !gst_structure_get_int (structure, "bitrate", &bitrate)
1612 || samplerate == 0 || channels == 0) {
1613 GST_WARNING_OBJECT (mux, "Missing wmaversion/block_align/bitrate/"
1614 "channels/rate on WMA caps");
1618 switch (wmaversion) {
1620 format = GST_RIFF_WAVE_FORMAT_WMAV1;
1623 format = GST_RIFF_WAVE_FORMAT_WMAV2;
1626 format = GST_RIFF_WAVE_FORMAT_WMAV3;
1629 GST_WARNING_OBJECT (mux, "Unexpected WMA version: %d", wmaversion);
1633 if (gst_structure_get_int (structure, "depth", &depth))
1634 audiocontext->bitdepth = depth;
1636 codec_priv_size = WAVEFORMATEX_SIZE;
1638 codec_priv_size += GST_BUFFER_SIZE (buf);
1640 /* serialize waveformatex structure */
1641 codec_priv = g_malloc0 (codec_priv_size);
1642 GST_WRITE_UINT16_LE (codec_priv, format);
1643 GST_WRITE_UINT16_LE (codec_priv + 2, channels);
1644 GST_WRITE_UINT32_LE (codec_priv + 4, samplerate);
1645 GST_WRITE_UINT32_LE (codec_priv + 8, bitrate / 8);
1646 GST_WRITE_UINT16_LE (codec_priv + 12, block_align);
1647 GST_WRITE_UINT16_LE (codec_priv + 14, 0);
1649 GST_WRITE_UINT16_LE (codec_priv + 16, GST_BUFFER_SIZE (buf));
1651 GST_WRITE_UINT16_LE (codec_priv + 16, 0);
1653 /* process codec private/initialization data, if any */
1655 memcpy ((guint8 *) codec_priv + WAVEFORMATEX_SIZE,
1656 GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
1659 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_ACM);
1660 context->codec_priv = (gpointer) codec_priv;
1661 context->codec_priv_size = codec_priv_size;
1669 GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT,
1670 GST_PAD_NAME (pad), caps);
1677 * gst_matroska_mux_subtitle_pad_setcaps:
1678 * @pad: Pad which got the caps.
1681 * Setcaps function for subtitle sink pad.
1683 * Returns: #TRUE on success.
1686 gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps)
1689 * Consider this as boilerplate code for now. There is
1690 * no single subtitle creation element in GStreamer,
1691 * neither do I know how subtitling works at all. */
1693 /* There is now (at least) one such alement (kateenc), and I'm going
1694 to handle it here and claim it works when it can be piped back
1695 through GStreamer and VLC */
1697 GstMatroskaTrackContext *context = NULL;
1698 GstMatroskaTrackSubtitleContext *scontext;
1699 GstMatroskaMux *mux;
1700 GstMatroskaPad *collect_pad;
1701 const gchar *mimetype;
1702 GstStructure *structure;
1704 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1707 collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
1708 g_assert (collect_pad);
1709 context = collect_pad->track;
1711 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_SUBTITLE);
1712 scontext = (GstMatroskaTrackSubtitleContext *) context;
1714 structure = gst_caps_get_structure (caps, 0);
1715 mimetype = gst_structure_get_name (structure);
1718 scontext->check_utf8 = 1;
1719 scontext->invalid_utf8 = 0;
1720 context->default_duration = 0;
1722 /* TODO: - other format than Kate */
1724 if (!strcmp (mimetype, "subtitle/x-kate")) {
1725 const GValue *streamheader;
1727 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_SUBTITLE_KATE);
1729 if (context->codec_priv != NULL) {
1730 g_free (context->codec_priv);
1731 context->codec_priv = NULL;
1732 context->codec_priv_size = 0;
1735 streamheader = gst_structure_get_value (structure, "streamheader");
1736 if (!kate_streamheader_to_codecdata (streamheader, context)) {
1737 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
1738 ("kate stream headers missing or malformed"));
1749 * gst_matroska_mux_request_new_pad:
1750 * @element: #GstMatroskaMux.
1751 * @templ: #GstPadTemplate.
1752 * @pad_name: New pad name.
1754 * Request pad function for sink templates.
1756 * Returns: New #GstPad.
1759 gst_matroska_mux_request_new_pad (GstElement * element,
1760 GstPadTemplate * templ, const gchar * pad_name)
1762 GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
1763 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
1764 GstMatroskaPad *collect_pad;
1765 GstPad *newpad = NULL;
1767 GstPadSetCapsFunction setcapsfunc = NULL;
1768 GstMatroskaTrackContext *context = NULL;
1770 if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
1771 name = g_strdup_printf ("audio_%d", mux->num_a_streams++);
1772 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_audio_pad_setcaps);
1773 context = (GstMatroskaTrackContext *)
1774 g_new0 (GstMatroskaTrackAudioContext, 1);
1775 context->type = GST_MATROSKA_TRACK_TYPE_AUDIO;
1776 context->name = g_strdup ("Audio");
1777 } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
1778 name = g_strdup_printf ("video_%d", mux->num_v_streams++);
1779 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_video_pad_setcaps);
1780 context = (GstMatroskaTrackContext *)
1781 g_new0 (GstMatroskaTrackVideoContext, 1);
1782 context->type = GST_MATROSKA_TRACK_TYPE_VIDEO;
1783 context->name = g_strdup ("Video");
1784 } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%d")) {
1785 name = g_strdup_printf ("subtitle_%d", mux->num_t_streams++);
1786 setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_subtitle_pad_setcaps);
1787 context = (GstMatroskaTrackContext *)
1788 g_new0 (GstMatroskaTrackSubtitleContext, 1);
1789 context->type = GST_MATROSKA_TRACK_TYPE_SUBTITLE;
1790 context->name = g_strdup ("Subtitle");
1792 GST_WARNING_OBJECT (mux, "This is not our template!");
1796 newpad = gst_pad_new_from_template (templ, name);
1798 collect_pad = (GstMatroskaPad *)
1799 gst_collect_pads_add_pad_full (mux->collect, newpad,
1800 sizeof (GstMatroskaPad),
1801 (GstCollectDataDestroyNotify) gst_matroska_pad_free);
1803 collect_pad->track = context;
1804 gst_matroska_pad_reset (collect_pad, FALSE);
1806 /* FIXME: hacked way to override/extend the event function of
1807 * GstCollectPads; because it sets its own event function giving the
1808 * element no access to events.
1809 * TODO GstCollectPads should really give its 'users' a clean chance to
1810 * properly handle events that are not meant for collectpads itself.
1811 * Perhaps a callback or so, though rejected (?) in #340060.
1812 * This would allow (clean) transcoding of info from demuxer/streams
1813 * to another muxer */
1814 mux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
1815 gst_pad_set_event_function (newpad,
1816 GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_sink_event));
1818 gst_pad_set_setcaps_function (newpad, setcapsfunc);
1819 gst_pad_set_active (newpad, TRUE);
1820 gst_element_add_pad (element, newpad);
1827 * gst_matroska_mux_release_pad:
1828 * @element: #GstMatroskaMux.
1829 * @pad: Pad to release.
1831 * Release a previously requested pad.
1834 gst_matroska_mux_release_pad (GstElement * element, GstPad * pad)
1836 GstMatroskaMux *mux;
1839 mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
1841 for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
1842 GstCollectData *cdata = (GstCollectData *) walk->data;
1843 GstMatroskaPad *collect_pad = (GstMatroskaPad *) cdata;
1845 if (cdata->pad == pad) {
1846 GstClockTime min_dur; /* observed minimum duration */
1848 if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) &&
1849 GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) {
1850 min_dur = GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts);
1851 if (collect_pad->duration < min_dur)
1852 collect_pad->duration = min_dur;
1855 if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
1856 mux->duration < collect_pad->duration)
1857 mux->duration = collect_pad->duration;
1863 gst_collect_pads_remove_pad (mux->collect, pad);
1864 if (gst_element_remove_pad (element, pad))
1870 * gst_matroska_mux_track_header:
1871 * @mux: #GstMatroskaMux
1872 * @context: Tack context.
1874 * Write a track header.
1877 gst_matroska_mux_track_header (GstMatroskaMux * mux,
1878 GstMatroskaTrackContext * context)
1880 GstEbmlWrite *ebml = mux->ebml_write;
1883 /* TODO: check if everything necessary is written and check default values */
1885 /* track type goes before the type-specific stuff */
1886 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKNUMBER, context->num);
1887 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKTYPE, context->type);
1889 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKUID,
1890 gst_matroska_mux_create_uid ());
1891 if (context->default_duration) {
1892 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKDEFAULTDURATION,
1893 context->default_duration);
1895 if (context->language) {
1896 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKLANGUAGE,
1900 /* type-specific stuff */
1901 switch (context->type) {
1902 case GST_MATROSKA_TRACK_TYPE_VIDEO:{
1903 GstMatroskaTrackVideoContext *videocontext =
1904 (GstMatroskaTrackVideoContext *) context;
1906 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKVIDEO);
1907 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELWIDTH,
1908 videocontext->pixel_width);
1909 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELHEIGHT,
1910 videocontext->pixel_height);
1911 if (videocontext->display_width && videocontext->display_height) {
1912 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYWIDTH,
1913 videocontext->display_width);
1914 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYHEIGHT,
1915 videocontext->display_height);
1917 if (context->flags & GST_MATROSKA_VIDEOTRACK_INTERLACED)
1918 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOFLAGINTERLACED, 1);
1919 if (videocontext->fourcc) {
1920 guint32 fcc_le = GUINT32_TO_LE (videocontext->fourcc);
1922 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_VIDEOCOLOURSPACE,
1923 (gpointer) & fcc_le, 4);
1925 gst_ebml_write_master_finish (ebml, master);
1930 case GST_MATROSKA_TRACK_TYPE_AUDIO:{
1931 GstMatroskaTrackAudioContext *audiocontext =
1932 (GstMatroskaTrackAudioContext *) context;
1934 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKAUDIO);
1935 if (audiocontext->samplerate != 8000)
1936 gst_ebml_write_float (ebml, GST_MATROSKA_ID_AUDIOSAMPLINGFREQ,
1937 audiocontext->samplerate);
1938 if (audiocontext->channels != 1)
1939 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOCHANNELS,
1940 audiocontext->channels);
1941 if (audiocontext->bitdepth) {
1942 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOBITDEPTH,
1943 audiocontext->bitdepth);
1945 gst_ebml_write_master_finish (ebml, master);
1951 /* doesn't need type-specific data */
1955 gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CODECID, context->codec_id);
1956 if (context->codec_priv)
1957 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECPRIVATE,
1958 context->codec_priv, context->codec_priv_size);
1959 /* FIXME: until we have a nice way of getting the codecname
1960 * out of the caps, I'm not going to enable this. Too much
1961 * (useless, double, boring) work... */
1962 /* TODO: Use value from tags if any */
1963 /*gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CODECNAME,
1964 context->codec_name); */
1965 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKNAME, context->name);
1970 * gst_matroska_mux_start:
1971 * @mux: #GstMatroskaMux
1973 * Start a new matroska file (write headers etc...)
1976 gst_matroska_mux_start (GstMatroskaMux * mux)
1978 GstEbmlWrite *ebml = mux->ebml_write;
1979 guint32 seekhead_id[] = { GST_MATROSKA_ID_SEGMENTINFO,
1980 GST_MATROSKA_ID_TRACKS,
1981 GST_MATROSKA_ID_CUES,
1982 GST_MATROSKA_ID_TAGS,
1985 guint64 master, child;
1989 GstClockTime duration = 0;
1990 guint32 segment_uid[4];
1991 GTimeVal time = { 0, 0 };
1993 /* we start with a EBML header */
1994 gst_ebml_write_header (ebml, "matroska", mux->matroska_version);
1996 /* start a segment */
1998 gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENT);
1999 mux->segment_master = ebml->pos;
2001 /* the rest of the header is cached */
2002 gst_ebml_write_set_cache (ebml, 0x1000);
2004 /* seekhead (table of contents) - we set the positions later */
2005 mux->seekhead_pos = ebml->pos;
2006 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD);
2007 for (i = 0; seekhead_id[i] != 0; i++) {
2008 child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKENTRY);
2009 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, seekhead_id[i]);
2010 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION, -1);
2011 gst_ebml_write_master_finish (ebml, child);
2013 gst_ebml_write_master_finish (ebml, master);
2016 mux->info_pos = ebml->pos;
2017 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENTINFO);
2018 for (i = 0; i < 4; i++) {
2019 segment_uid[i] = g_random_int ();
2021 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_SEGMENTUID,
2022 (guint8 *) segment_uid, 16);
2023 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TIMECODESCALE, mux->time_scale);
2024 mux->duration_pos = ebml->pos;
2026 for (collected = mux->collect->data; collected;
2027 collected = g_slist_next (collected)) {
2028 GstMatroskaPad *collect_pad;
2029 GstFormat format = GST_FORMAT_TIME;
2031 gint64 trackduration;
2033 collect_pad = (GstMatroskaPad *) collected->data;
2034 thepad = collect_pad->collect.pad;
2036 /* Query the total length of the track. */
2037 GST_DEBUG_OBJECT (thepad, "querying peer duration");
2038 if (gst_pad_query_peer_duration (thepad, &format, &trackduration)) {
2039 GST_DEBUG_OBJECT (thepad, "duration: %" GST_TIME_FORMAT,
2040 GST_TIME_ARGS (trackduration));
2041 if (trackduration != GST_CLOCK_TIME_NONE && trackduration > duration) {
2042 duration = (GstClockTime) trackduration;
2046 gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
2047 gst_guint64_to_gdouble (duration) /
2048 gst_guint64_to_gdouble (mux->time_scale));
2050 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_MUXINGAPP,
2051 "GStreamer plugin version " PACKAGE_VERSION);
2052 if (mux->writing_app && mux->writing_app[0]) {
2053 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_WRITINGAPP, mux->writing_app);
2055 g_get_current_time (&time);
2056 gst_ebml_write_date (ebml, GST_MATROSKA_ID_DATEUTC, time.tv_sec);
2057 gst_ebml_write_master_finish (ebml, master);
2060 mux->tracks_pos = ebml->pos;
2061 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKS);
2063 for (collected = mux->collect->data; collected;
2064 collected = g_slist_next (collected)) {
2065 GstMatroskaPad *collect_pad;
2068 collect_pad = (GstMatroskaPad *) collected->data;
2069 thepad = collect_pad->collect.pad;
2071 if (gst_pad_is_linked (thepad) && gst_pad_is_active (thepad) &&
2072 collect_pad->track->codec_id != 0) {
2073 collect_pad->track->num = tracknum++;
2074 child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKENTRY);
2075 gst_matroska_mux_track_header (mux, collect_pad->track);
2076 gst_ebml_write_master_finish (ebml, child);
2079 gst_ebml_write_master_finish (ebml, master);
2081 /* lastly, flush the cache */
2082 gst_ebml_write_flush_cache (ebml);
2086 gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag,
2089 /* TODO: more sensible tag mappings */
2092 const gchar *matroska_tagname;
2093 const gchar *gstreamer_tagname;
2097 GST_MATROSKA_TAG_ID_TITLE, GST_TAG_TITLE}, {
2098 GST_MATROSKA_TAG_ID_AUTHOR, GST_TAG_ARTIST}, {
2099 GST_MATROSKA_TAG_ID_ALBUM, GST_TAG_ALBUM}, {
2100 GST_MATROSKA_TAG_ID_COMMENTS, GST_TAG_COMMENT}, {
2101 GST_MATROSKA_TAG_ID_BITSPS, GST_TAG_BITRATE}, {
2102 GST_MATROSKA_TAG_ID_BPS, GST_TAG_BITRATE}, {
2103 GST_MATROSKA_TAG_ID_ENCODER, GST_TAG_ENCODER}, {
2104 GST_MATROSKA_TAG_ID_DATE, GST_TAG_DATE}, {
2105 GST_MATROSKA_TAG_ID_ISRC, GST_TAG_ISRC}, {
2106 GST_MATROSKA_TAG_ID_COPYRIGHT, GST_TAG_COPYRIGHT}, {
2107 GST_MATROSKA_TAG_ID_BPM, GST_TAG_BEATS_PER_MINUTE}, {
2108 GST_MATROSKA_TAG_ID_TERMS_OF_USE, GST_TAG_LICENSE}, {
2109 GST_MATROSKA_TAG_ID_COMPOSER, GST_TAG_COMPOSER}, {
2110 GST_MATROSKA_TAG_ID_LEAD_PERFORMER, GST_TAG_PERFORMER}, {
2111 GST_MATROSKA_TAG_ID_GENRE, GST_TAG_GENRE}
2113 GstEbmlWrite *ebml = (GstEbmlWrite *) data;
2115 guint64 simpletag_master;
2117 for (i = 0; i < G_N_ELEMENTS (tag_conv); i++) {
2118 const gchar *tagname_gst = tag_conv[i].gstreamer_tagname;
2119 const gchar *tagname_mkv = tag_conv[i].matroska_tagname;
2121 if (strcmp (tagname_gst, tag) == 0) {
2122 GValue src = { 0, };
2125 if (!gst_tag_list_copy_value (&src, list, tag))
2127 if ((dest = gst_value_serialize (&src))) {
2129 simpletag_master = gst_ebml_write_master_start (ebml,
2130 GST_MATROSKA_ID_SIMPLETAG);
2131 gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_TAGNAME, tagname_mkv);
2132 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TAGSTRING, dest);
2133 gst_ebml_write_master_finish (ebml, simpletag_master);
2136 GST_WARNING ("Can't transform tag '%s' to string", tagname_mkv);
2138 g_value_unset (&src);
2146 * gst_matroska_mux_finish:
2147 * @mux: #GstMatroskaMux
2149 * Finish a new matroska file (write index etc...)
2152 gst_matroska_mux_finish (GstMatroskaMux * mux)
2154 GstEbmlWrite *ebml = mux->ebml_write;
2156 guint64 duration = 0;
2158 const GstTagList *tags;
2160 /* finish last cluster */
2162 gst_ebml_write_master_finish (ebml, mux->cluster);
2166 if (mux->index != NULL) {
2168 guint64 master, pointentry_master, trackpos_master;
2170 mux->cues_pos = ebml->pos;
2171 gst_ebml_write_set_cache (ebml, 12 + 41 * mux->num_indexes);
2172 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CUES);
2174 for (n = 0; n < mux->num_indexes; n++) {
2175 GstMatroskaIndex *idx = &mux->index[n];
2177 pointentry_master = gst_ebml_write_master_start (ebml,
2178 GST_MATROSKA_ID_POINTENTRY);
2179 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETIME,
2180 idx->time / mux->time_scale);
2181 trackpos_master = gst_ebml_write_master_start (ebml,
2182 GST_MATROSKA_ID_CUETRACKPOSITIONS);
2183 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETRACK, idx->track);
2184 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUECLUSTERPOSITION,
2185 idx->pos - mux->segment_master);
2186 gst_ebml_write_master_finish (ebml, trackpos_master);
2187 gst_ebml_write_master_finish (ebml, pointentry_master);
2190 gst_ebml_write_master_finish (ebml, master);
2191 gst_ebml_write_flush_cache (ebml);
2195 tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (mux));
2197 if (tags != NULL && !gst_tag_list_is_empty (tags)) {
2198 guint64 master_tags, master_tag;
2200 GST_DEBUG ("Writing tags");
2202 /* TODO: maybe limit via the TARGETS id by looking at the source pad */
2203 mux->tags_pos = ebml->pos;
2204 master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS);
2205 master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG);
2206 gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml);
2207 gst_ebml_write_master_finish (ebml, master_tag);
2208 gst_ebml_write_master_finish (ebml, master_tags);
2211 /* update seekhead. We know that:
2212 * - a seekhead contains 4 entries.
2213 * - order of entries is as above.
2214 * - a seekhead has a 4-byte header + 8-byte length
2215 * - each entry is 2-byte master, 2-byte ID pointer,
2216 * 2-byte length pointer, all 8/1-byte length, 4-
2217 * byte ID and 8-byte length pointer, where the
2218 * length pointer starts at 20.
2219 * - all entries are local to the segment (so pos - segment_master).
2220 * - so each entry is at 12 + 20 + num * 28. */
2221 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 32,
2222 mux->info_pos - mux->segment_master);
2223 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 60,
2224 mux->tracks_pos - mux->segment_master);
2225 if (mux->index != NULL) {
2226 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88,
2227 mux->cues_pos - mux->segment_master);
2230 guint64 my_pos = ebml->pos;
2232 gst_ebml_write_seek (ebml, mux->seekhead_pos + 68);
2233 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
2234 gst_ebml_write_seek (ebml, my_pos);
2237 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116,
2238 mux->tags_pos - mux->segment_master);
2241 guint64 my_pos = ebml->pos;
2243 gst_ebml_write_seek (ebml, mux->seekhead_pos + 96);
2244 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
2245 gst_ebml_write_seek (ebml, my_pos);
2248 /* update duration */
2249 /* first get the overall duration */
2250 /* a released track may have left a duration in here */
2251 duration = mux->duration;
2252 for (collected = mux->collect->data; collected;
2253 collected = g_slist_next (collected)) {
2254 GstMatroskaPad *collect_pad;
2255 GstClockTime min_duration; /* observed minimum duration */
2257 collect_pad = (GstMatroskaPad *) collected->data;
2259 GST_DEBUG_OBJECT (mux, "Pad %" GST_PTR_FORMAT " start ts %" GST_TIME_FORMAT
2260 " end ts %" GST_TIME_FORMAT, collect_pad,
2261 GST_TIME_ARGS (collect_pad->start_ts),
2262 GST_TIME_ARGS (collect_pad->end_ts));
2264 if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) &&
2265 GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) {
2267 GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts);
2268 if (collect_pad->duration < min_duration)
2269 collect_pad->duration = min_duration;
2270 GST_DEBUG_OBJECT (collect_pad, "final track duration: %" GST_TIME_FORMAT,
2271 GST_TIME_ARGS (collect_pad->duration));
2274 if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
2275 duration < collect_pad->duration)
2276 duration = collect_pad->duration;
2278 if (duration != 0) {
2279 GST_DEBUG_OBJECT (mux, "final total duration: %" GST_TIME_FORMAT,
2280 GST_TIME_ARGS (duration));
2281 pos = mux->ebml_write->pos;
2282 gst_ebml_write_seek (ebml, mux->duration_pos);
2283 gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
2284 gst_guint64_to_gdouble (duration) /
2285 gst_guint64_to_gdouble (mux->time_scale));
2286 gst_ebml_write_seek (ebml, pos);
2289 guint64 my_pos = ebml->pos;
2291 gst_ebml_write_seek (ebml, mux->duration_pos);
2292 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 8);
2293 gst_ebml_write_seek (ebml, my_pos);
2296 /* finish segment - this also writes element length */
2297 gst_ebml_write_master_finish (ebml, mux->segment_pos);
2302 * gst_matroska_mux_best_pad:
2303 * @mux: #GstMatroskaMux
2304 * @popped: True if at least one buffer was popped from #GstCollectPads
2306 * Find a pad with the oldest data
2307 * (data from this pad should be written first).
2309 * Returns: Selected pad.
2311 static GstMatroskaPad *
2312 gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped)
2315 GstMatroskaPad *best = NULL;
2318 for (collected = mux->collect->data; collected;
2319 collected = g_slist_next (collected)) {
2320 GstMatroskaPad *collect_pad;
2322 collect_pad = (GstMatroskaPad *) collected->data;
2323 /* fetch a new buffer if needed */
2324 if (collect_pad->buffer == NULL) {
2325 collect_pad->buffer = gst_collect_pads_pop (mux->collect,
2326 (GstCollectData *) collect_pad);
2328 if (collect_pad->buffer != NULL)
2332 /* if we have a buffer check if it is better then the current best one */
2333 if (collect_pad->buffer != NULL) {
2334 if (best == NULL || !GST_BUFFER_TIMESTAMP_IS_VALID (collect_pad->buffer)
2335 || (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)
2336 && GST_BUFFER_TIMESTAMP (collect_pad->buffer) <
2337 GST_BUFFER_TIMESTAMP (best->buffer))) {
2347 * gst_matroska_mux_buffer_header:
2348 * @track: Track context.
2349 * @relative_timestamp: relative timestamp of the buffer
2350 * @flags: Buffer flags.
2352 * Create a buffer containing buffer header.
2354 * Returns: New buffer.
2357 gst_matroska_mux_create_buffer_header (GstMatroskaTrackContext * track,
2358 gint16 relative_timestamp, int flags)
2362 hdr = gst_buffer_new_and_alloc (4);
2363 /* track num - FIXME: what if num >= 0x80 (unlikely)? */
2364 GST_BUFFER_DATA (hdr)[0] = track->num | 0x80;
2365 /* time relative to clustertime */
2366 GST_WRITE_UINT16_BE (GST_BUFFER_DATA (hdr) + 1, relative_timestamp);
2369 GST_BUFFER_DATA (hdr)[3] = flags;
2374 #define DIRAC_PARSE_CODE_SEQUENCE_HEADER 0x00
2375 #define DIRAC_PARSE_CODE_END_OF_SEQUENCE 0x10
2376 #define DIRAC_PARSE_CODE_IS_PICTURE(x) ((x & 0x08) != 0)
2379 gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux,
2380 GstMatroskaPad * collect_pad, GstBuffer * buf)
2382 GstMatroskaTrackVideoContext *ctx =
2383 (GstMatroskaTrackVideoContext *) collect_pad->track;
2384 const guint8 *data = GST_BUFFER_DATA (buf);
2385 guint size = GST_BUFFER_SIZE (buf);
2387 guint32 next_parse_offset;
2388 GstBuffer *ret = NULL;
2389 gboolean is_muxing_unit = FALSE;
2391 if (GST_BUFFER_SIZE (buf) < 13) {
2392 gst_buffer_unref (buf);
2396 /* Check if this buffer contains a picture or end-of-sequence packet */
2397 while (size >= 13) {
2398 if (GST_READ_UINT32_BE (data) != 0x42424344 /* 'BBCD' */ ) {
2399 gst_buffer_unref (buf);
2403 parse_code = GST_READ_UINT8 (data + 4);
2404 if (parse_code == DIRAC_PARSE_CODE_SEQUENCE_HEADER) {
2405 if (ctx->dirac_unit) {
2406 gst_buffer_unref (ctx->dirac_unit);
2407 ctx->dirac_unit = NULL;
2409 } else if (DIRAC_PARSE_CODE_IS_PICTURE (parse_code) ||
2410 parse_code == DIRAC_PARSE_CODE_END_OF_SEQUENCE) {
2411 is_muxing_unit = TRUE;
2415 next_parse_offset = GST_READ_UINT32_BE (data + 5);
2417 if (G_UNLIKELY (next_parse_offset == 0))
2420 data += next_parse_offset;
2421 size -= next_parse_offset;
2424 if (ctx->dirac_unit)
2425 ctx->dirac_unit = gst_buffer_join (ctx->dirac_unit, gst_buffer_ref (buf));
2427 ctx->dirac_unit = gst_buffer_ref (buf);
2429 if (is_muxing_unit) {
2430 ret = gst_buffer_make_metadata_writable (ctx->dirac_unit);
2431 ctx->dirac_unit = NULL;
2432 gst_buffer_copy_metadata (ret, buf,
2433 GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS |
2434 GST_BUFFER_COPY_CAPS);
2435 gst_buffer_unref (buf);
2437 gst_buffer_unref (buf);
2445 * gst_matroska_mux_write_data:
2446 * @mux: #GstMatroskaMux
2447 * @collect_pad: #GstMatroskaPad with the data
2449 * Write collected data (called from gst_matroska_mux_collected).
2451 * Returns: Result of the gst_pad_push issued to write the data.
2453 static GstFlowReturn
2454 gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
2456 GstEbmlWrite *ebml = mux->ebml_write;
2457 GstBuffer *buf, *hdr;
2459 gboolean write_duration;
2460 gint16 relative_timestamp;
2461 gint64 relative_timestamp64;
2462 guint64 block_duration;
2463 gboolean is_video_keyframe = FALSE;
2466 buf = collect_pad->buffer;
2467 collect_pad->buffer = NULL;
2469 /* vorbis/theora headers are retrieved from caps and put in CodecPrivate */
2470 if (collect_pad->track->xiph_headers_to_skip > 0) {
2471 GST_LOG_OBJECT (collect_pad->collect.pad, "dropping streamheader buffer");
2472 gst_buffer_unref (buf);
2473 --collect_pad->track->xiph_headers_to_skip;
2477 /* for dirac we have to queue up everything up to a picture unit */
2478 if (collect_pad->track->codec_id != NULL &&
2479 strcmp (collect_pad->track->codec_id,
2480 GST_MATROSKA_CODEC_ID_VIDEO_DIRAC) == 0) {
2481 buf = gst_matroska_mux_handle_dirac_packet (mux, collect_pad, buf);
2486 /* hm, invalid timestamp (due to --to be fixed--- element upstream);
2487 * this would wreak havoc with time stored in matroska file */
2488 /* TODO: maybe calculate a timestamp by using the previous timestamp
2489 * and default duration */
2490 if (!GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
2491 GST_WARNING_OBJECT (collect_pad->collect.pad,
2492 "Invalid buffer timestamp; dropping buffer");
2493 gst_buffer_unref (buf);
2497 /* set the timestamp for outgoing buffers */
2498 ebml->timestamp = GST_BUFFER_TIMESTAMP (buf);
2500 if (collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_VIDEO &&
2501 !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
2502 GST_LOG_OBJECT (mux, "have video keyframe, ts=%" GST_TIME_FORMAT,
2503 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
2504 is_video_keyframe = TRUE;
2508 /* start a new cluster every two seconds or at keyframe */
2509 if (mux->cluster_time + GST_SECOND * 2 < GST_BUFFER_TIMESTAMP (buf)
2510 || is_video_keyframe) {
2512 gst_ebml_write_master_finish (ebml, mux->cluster);
2513 mux->prev_cluster_size = ebml->pos - mux->cluster_pos;
2514 mux->cluster_pos = ebml->pos;
2516 gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
2517 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
2518 GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
2519 mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
2520 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_PREVSIZE,
2521 mux->prev_cluster_size);
2526 mux->cluster_pos = ebml->pos;
2527 mux->cluster = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
2528 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
2529 GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
2530 mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
2533 /* update duration of this track */
2534 if (GST_BUFFER_DURATION_IS_VALID (buf))
2535 collect_pad->duration += GST_BUFFER_DURATION (buf);
2537 /* We currently write index entries for all video tracks or for the audio
2538 * track in a single-track audio file. This could be improved by keeping the
2539 * index only for the *first* video track. */
2541 /* TODO: index is useful for every track, should contain the number of
2542 * the block in the cluster which contains the timestamp, should also work
2543 * for files with multiple audio tracks.
2545 if (is_video_keyframe ||
2546 ((collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_AUDIO) &&
2547 (mux->num_streams == 1))) {
2550 if (mux->min_index_interval != 0) {
2551 for (last_idx = mux->num_indexes - 1; last_idx >= 0; last_idx--) {
2552 if (mux->index[last_idx].track == collect_pad->track->num)
2557 if (last_idx < 0 || mux->min_index_interval == 0 ||
2558 (GST_CLOCK_DIFF (mux->index[last_idx].time, GST_BUFFER_TIMESTAMP (buf))
2559 >= mux->min_index_interval)) {
2560 GstMatroskaIndex *idx;
2562 if (mux->num_indexes % 32 == 0) {
2563 mux->index = g_renew (GstMatroskaIndex, mux->index,
2564 mux->num_indexes + 32);
2566 idx = &mux->index[mux->num_indexes++];
2568 idx->pos = mux->cluster_pos;
2569 idx->time = GST_BUFFER_TIMESTAMP (buf);
2570 idx->track = collect_pad->track->num;
2574 /* Check if the duration differs from the default duration. */
2575 write_duration = FALSE;
2576 block_duration = GST_BUFFER_DURATION (buf);
2577 if (GST_BUFFER_DURATION_IS_VALID (buf)) {
2578 if (block_duration != collect_pad->track->default_duration) {
2579 write_duration = TRUE;
2583 /* write the block, for matroska v2 use SimpleBlock if possible
2584 * one slice (*breath*).
2585 * FIXME: Need to do correct lacing! */
2586 relative_timestamp64 = GST_BUFFER_TIMESTAMP (buf) - mux->cluster_time;
2587 if (relative_timestamp64 >= 0) {
2588 /* round the timestamp */
2589 relative_timestamp64 += mux->time_scale / 2;
2591 /* round the timestamp */
2592 relative_timestamp64 -= mux->time_scale / 2;
2594 relative_timestamp = relative_timestamp64 / (gint64) mux->time_scale;
2595 if (mux->matroska_version > 1 && !write_duration) {
2597 GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) ? 0 : 0x80;
2600 gst_matroska_mux_create_buffer_header (collect_pad->track,
2601 relative_timestamp, flags);
2602 gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_SIMPLEBLOCK,
2603 GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
2604 gst_ebml_write_buffer (ebml, hdr);
2605 gst_ebml_write_buffer (ebml, buf);
2607 return gst_ebml_last_write_result (ebml);
2609 blockgroup = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_BLOCKGROUP);
2611 gst_matroska_mux_create_buffer_header (collect_pad->track,
2612 relative_timestamp, 0);
2613 gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_BLOCK,
2614 GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
2615 gst_ebml_write_buffer (ebml, hdr);
2616 gst_ebml_write_buffer (ebml, buf);
2617 if (write_duration) {
2618 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_BLOCKDURATION,
2619 block_duration / mux->time_scale);
2621 gst_ebml_write_master_finish (ebml, blockgroup);
2622 return gst_ebml_last_write_result (ebml);
2628 * gst_matroska_mux_collected:
2629 * @pads: #GstCollectPads
2630 * @uuser_data: #GstMatroskaMux
2632 * Collectpads callback.
2634 * Returns: #GstFlowReturn
2636 static GstFlowReturn
2637 gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data)
2639 GstMatroskaMux *mux = GST_MATROSKA_MUX (user_data);
2640 GstMatroskaPad *best;
2644 GST_DEBUG_OBJECT (mux, "Collected pads");
2646 /* start with a header */
2647 if (mux->state == GST_MATROSKA_MUX_STATE_START) {
2648 if (mux->collect->data == NULL) {
2649 GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
2650 ("No input streams configured"));
2651 return GST_FLOW_ERROR;
2653 mux->state = GST_MATROSKA_MUX_STATE_HEADER;
2654 gst_matroska_mux_start (mux);
2655 mux->state = GST_MATROSKA_MUX_STATE_DATA;
2659 /* which stream to write from? */
2660 best = gst_matroska_mux_best_pad (mux, &popped);
2662 /* if there is no best pad, we have reached EOS */
2664 GST_DEBUG_OBJECT (mux, "No best pad finishing...");
2665 gst_matroska_mux_finish (mux);
2666 gst_pad_push_event (mux->srcpad, gst_event_new_eos ());
2667 ret = GST_FLOW_UNEXPECTED;
2670 GST_DEBUG_OBJECT (best->collect.pad, "best pad - buffer ts %"
2671 GST_TIME_FORMAT " dur %" GST_TIME_FORMAT,
2672 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (best->buffer)),
2673 GST_TIME_ARGS (GST_BUFFER_DURATION (best->buffer)));
2675 /* make note of first and last encountered timestamps, so we can calculate
2676 * the actual duration later when we send an updated header on eos */
2677 if (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)) {
2678 GstClockTime start_ts = GST_BUFFER_TIMESTAMP (best->buffer);
2679 GstClockTime end_ts = start_ts;
2681 if (GST_BUFFER_DURATION_IS_VALID (best->buffer))
2682 end_ts += GST_BUFFER_DURATION (best->buffer);
2683 else if (best->track->default_duration)
2684 end_ts += best->track->default_duration;
2686 if (!GST_CLOCK_TIME_IS_VALID (best->end_ts) || end_ts > best->end_ts)
2687 best->end_ts = end_ts;
2689 if (G_UNLIKELY (best->start_ts == GST_CLOCK_TIME_NONE ||
2690 start_ts < best->start_ts))
2691 best->start_ts = start_ts;
2694 /* write one buffer */
2695 ret = gst_matroska_mux_write_data (mux, best);
2696 } while (ret == GST_FLOW_OK && !popped);
2703 * gst_matroska_mux_change_state:
2704 * @element: #GstMatroskaMux
2705 * @transition: State change transition.
2707 * Change the muxer state.
2709 * Returns: #GstStateChangeReturn
2711 static GstStateChangeReturn
2712 gst_matroska_mux_change_state (GstElement * element, GstStateChange transition)
2714 GstStateChangeReturn ret;
2715 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
2717 switch (transition) {
2718 case GST_STATE_CHANGE_NULL_TO_READY:
2720 case GST_STATE_CHANGE_READY_TO_PAUSED:
2721 gst_collect_pads_start (mux->collect);
2723 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
2725 case GST_STATE_CHANGE_PAUSED_TO_READY:
2726 gst_collect_pads_stop (mux->collect);
2732 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2734 switch (transition) {
2735 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
2737 case GST_STATE_CHANGE_PAUSED_TO_READY:
2738 gst_matroska_mux_reset (GST_ELEMENT (mux));
2740 case GST_STATE_CHANGE_READY_TO_NULL:
2750 gst_matroska_mux_set_property (GObject * object,
2751 guint prop_id, const GValue * value, GParamSpec * pspec)
2753 GstMatroskaMux *mux;
2755 g_return_if_fail (GST_IS_MATROSKA_MUX (object));
2756 mux = GST_MATROSKA_MUX (object);
2759 case ARG_WRITING_APP:
2760 if (!g_value_get_string (value)) {
2761 GST_WARNING_OBJECT (mux, "writing-app property can not be NULL");
2764 g_free (mux->writing_app);
2765 mux->writing_app = g_value_dup_string (value);
2767 case ARG_MATROSKA_VERSION:
2768 mux->matroska_version = g_value_get_int (value);
2770 case ARG_MIN_INDEX_INTERVAL:
2771 mux->min_index_interval = g_value_get_int64 (value);
2774 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2780 gst_matroska_mux_get_property (GObject * object,
2781 guint prop_id, GValue * value, GParamSpec * pspec)
2783 GstMatroskaMux *mux;
2785 g_return_if_fail (GST_IS_MATROSKA_MUX (object));
2786 mux = GST_MATROSKA_MUX (object);
2789 case ARG_WRITING_APP:
2790 g_value_set_string (value, mux->writing_app);
2792 case ARG_MATROSKA_VERSION:
2793 g_value_set_int (value, mux->matroska_version);
2795 case ARG_MIN_INDEX_INTERVAL:
2796 g_value_set_int64 (value, mux->min_index_interval);
2799 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2805 gst_matroska_mux_plugin_init (GstPlugin * plugin)
2807 return gst_element_register (plugin, "matroskamux",
2808 GST_RANK_PRIMARY, GST_TYPE_MATROSKA_MUX);