1 /* GStreamer Matroska muxer/demuxer
2 * (c) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
4 * matroska-mux.c: matroska file/stream muxer
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
29 #include "matroska-mux.h"
30 #include "matroska-ids.h"
43 GST_PAD_TEMPLATE_FACTORY (src_templ,
56 GST_PAD_TEMPLATE_FACTORY (videosink_templ,
63 GST_PAD_TEMPLATE_FACTORY (audiosink_templ,
70 GST_PAD_TEMPLATE_FACTORY (subtitlesink_templ,
77 /* gobject magic foo */
78 static void gst_matroska_mux_base_init (GstMatroskaMuxClass *klass);
79 static void gst_matroska_mux_class_init (GstMatroskaMuxClass *klass);
80 static void gst_matroska_mux_init (GstMatroskaMux *mux);
82 /* element functions */
83 static void gst_matroska_mux_loop (GstElement *element);
86 static GstPad * gst_matroska_mux_request_new_pad (GstElement *element,
87 GstPadTemplate *templ,
90 /* gst internal change state handler */
91 static GstElementStateReturn
92 gst_matroska_mux_change_state (GstElement *element);
95 static void gst_matroska_mux_set_property (GObject *object,
99 static void gst_matroska_mux_get_property (GObject *object,
105 static void gst_matroska_mux_reset (GstElement *element);
107 static GstEbmlWriteClass *parent_class = NULL;
108 /*static guint gst_matroska_mux_signals[LAST_SIGNAL] = { 0 };*/
111 gst_matroska_mux_get_type (void)
113 static GType gst_matroska_mux_type = 0;
115 if (!gst_matroska_mux_type) {
116 static const GTypeInfo gst_matroska_mux_info = {
117 sizeof (GstMatroskaMuxClass),
118 (GBaseInitFunc) gst_matroska_mux_base_init,
120 (GClassInitFunc) gst_matroska_mux_class_init,
123 sizeof (GstMatroskaMux),
125 (GInstanceInitFunc) gst_matroska_mux_init,
128 gst_matroska_mux_type =
129 g_type_register_static (GST_TYPE_EBML_WRITE,
131 &gst_matroska_mux_info, 0);
134 return gst_matroska_mux_type;
138 gst_matroska_mux_base_init (GstMatroskaMuxClass *klass)
140 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
141 static GstElementDetails gst_matroska_mux_details = {
144 "Muxes video/audio/subtitle streams into a matroska stream",
145 "Ronald Bultje <rbultje@ronald.bitfreak.net>"
148 gst_element_class_add_pad_template (element_class,
149 GST_PAD_TEMPLATE_GET (videosink_templ));
150 gst_element_class_add_pad_template (element_class,
151 GST_PAD_TEMPLATE_GET (audiosink_templ));
152 gst_element_class_add_pad_template (element_class,
153 GST_PAD_TEMPLATE_GET (subtitlesink_templ));
154 gst_element_class_add_pad_template (element_class,
155 GST_PAD_TEMPLATE_GET (src_templ));
156 gst_element_class_set_details (element_class,
157 &gst_matroska_mux_details);
161 gst_matroska_mux_class_init (GstMatroskaMuxClass *klass)
163 GObjectClass *gobject_class;
164 GstElementClass *gstelement_class;
166 gobject_class = (GObjectClass *) klass;
167 gstelement_class = (GstElementClass *) klass;
169 g_object_class_install_property (gobject_class, ARG_METADATA,
170 g_param_spec_boxed ("metadata", "Metadata", "Metadata",
171 GST_TYPE_CAPS, G_PARAM_READWRITE));
173 parent_class = g_type_class_ref (GST_TYPE_EBML_WRITE);
175 gobject_class->get_property = gst_matroska_mux_get_property;
176 gobject_class->set_property = gst_matroska_mux_set_property;
178 gstelement_class->change_state = gst_matroska_mux_change_state;
179 gstelement_class->request_new_pad = gst_matroska_mux_request_new_pad;
183 gst_matroska_mux_init (GstMatroskaMux *mux)
185 GstElementClass *klass = GST_ELEMENT_GET_CLASS (mux);
188 mux->srcpad = gst_pad_new_from_template (
189 gst_element_class_get_pad_template (klass, "src"), "src");
190 gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad);
191 GST_EBML_WRITE (mux)->srcpad = mux->srcpad;
193 gst_element_set_loop_function (GST_ELEMENT (mux),
194 gst_matroska_mux_loop);
196 /* initial stream no. */
197 for (i = 0; i < GST_MATROSKA_MUX_MAX_STREAMS; i++) {
198 mux->sink[i].buffer = NULL;
199 mux->sink[i].track = NULL;
204 gst_matroska_mux_reset (GST_ELEMENT (mux));
208 gst_matroska_mux_reset (GstElement *element)
210 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
214 mux->state = GST_MATROSKA_MUX_STATE_START;
216 /* clean up existing streams */
217 for (i = 0; i < GST_MATROSKA_MUX_MAX_STREAMS; i++) {
218 if (mux->sink[i].track != NULL) {
219 if (mux->sink[i].track->pad != NULL) {
220 gst_element_remove_pad (GST_ELEMENT (mux), mux->sink[i].track->pad);
222 g_free (mux->sink[i].track->codec_id);
223 g_free (mux->sink[i].track->codec_name);
224 g_free (mux->sink[i].track->name);
225 g_free (mux->sink[i].track->language);
226 g_free (mux->sink[i].track->codec_priv);
227 g_free (mux->sink[i].track);
228 mux->sink[i].track = NULL;
230 if (mux->sink[i].buffer != NULL) {
231 gst_buffer_unref (mux->sink[i].buffer);
232 mux->sink[i].buffer = NULL;
234 mux->sink[i].eos = FALSE;
236 mux->num_streams = 0;
237 mux->num_a_streams = 0;
238 mux->num_t_streams = 0;
239 mux->num_v_streams = 0;
241 /* reset media info (to default) */
242 gst_caps_replace (&mux->metadata,
243 GST_CAPS_NEW ("matroska_metadata",
244 "application/x-gst-metadata",
245 "application", GST_PROPS_STRING (""),
246 "date", GST_PROPS_STRING ("")));
249 mux->num_indexes = 0;
254 mux->time_scale = 1000000;
258 static GstPadLinkReturn
259 gst_matroska_mux_video_pad_link (GstPad *pad,
262 GstMatroskaTrackContext *context = NULL;
263 GstMatroskaTrackVideoContext *videocontext;
264 GstMatroskaMux *mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad));
265 const gchar *mimetype;
266 gint width, height, pixel_width, pixel_height, i;
269 if (!GST_CAPS_IS_FIXED (caps))
270 return GST_PAD_LINK_DELAYED;
273 for (i = 0; i < mux->num_streams; i++) {
274 if (mux->sink[i].track && mux->sink[i].track->pad &&
275 mux->sink[i].track->pad == pad) {
276 context = mux->sink[i].track;
280 g_assert (i < mux->num_streams);
281 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO);
282 videocontext = (GstMatroskaTrackVideoContext *) context;
284 /* gst -> matroska ID'ing */
285 for (; caps != NULL; caps = caps->next) {
286 mimetype = gst_caps_get_mime (caps);
288 /* get general properties */
292 "framerate", &framerate,
294 videocontext->pixel_width = width;
295 videocontext->pixel_height = height;
296 context->default_duration = GST_SECOND / framerate;
298 if (gst_caps_has_property (caps, "pixel_width") &&
299 gst_caps_has_property (caps, "pixel_height")) {
301 "pixel_width", &pixel_width,
302 "pixel_height", &pixel_height,
304 if (pixel_width > pixel_height) {
305 videocontext->display_width = width * pixel_width / pixel_height;
306 videocontext->display_height = height;
307 } else if (pixel_width < pixel_height) {
308 videocontext->display_width = width;
309 videocontext->display_height = height * pixel_height / pixel_width;
311 videocontext->display_width = 0;
312 videocontext->display_height = 0;
315 videocontext->display_width = 0;
316 videocontext->display_height = 0;
319 videocontext->asr_mode = GST_MATROSKA_ASPECT_RATIO_MODE_FREE;
320 videocontext->eye_mode = GST_MATROSKA_EYE_MODE_MONO;
321 videocontext->fourcc = 0;
324 if (!strcmp (mimetype, "video/x-raw-yuv")) {
325 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED);
326 gst_caps_get_fourcc_int (caps, "format", &videocontext->fourcc);
328 return GST_PAD_LINK_OK;
329 } else if (!strcmp (mimetype, "video/x-jpeg")) {
330 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
332 return GST_PAD_LINK_OK;
333 } else if (!strcmp (mimetype, "video/x-divx")) {
336 gst_caps_get_int (caps, "divxversion", &divxversion);
337 switch (divxversion) {
339 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3);
342 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_SP);
345 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
349 return GST_PAD_LINK_OK;
350 } else if (!strcmp (mimetype, "video/x-xvid")) {
351 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
353 return GST_PAD_LINK_OK;
354 } else if (!strcmp (mimetype, "video/mpeg")) {
357 gst_caps_get_int (caps, "mpegversion", &mpegversion);
358 switch (mpegversion) {
360 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG1);
363 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG2);
366 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
370 return GST_PAD_LINK_OK;
371 } else if (!strcmp (mimetype, "video/x-msmpeg")) {
372 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3);
374 return GST_PAD_LINK_OK;
378 return GST_PAD_LINK_REFUSED;
381 static GstPadLinkReturn
382 gst_matroska_mux_audio_pad_link (GstPad *pad,
385 GstMatroskaTrackContext *context = NULL;
386 GstMatroskaTrackAudioContext *audiocontext;
387 GstMatroskaMux *mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad));
388 const gchar *mimetype;
389 gint samplerate, channels, i;
391 if (!GST_CAPS_IS_FIXED (caps))
392 return GST_PAD_LINK_DELAYED;
395 for (i = 0; i < mux->num_streams; i++) {
396 if (mux->sink[i].track && mux->sink[i].track->pad &&
397 mux->sink[i].track->pad == pad) {
398 context = mux->sink[i].track;
402 g_assert (i < mux->num_streams);
403 g_assert (context->type == GST_MATROSKA_TRACK_TYPE_AUDIO);
404 audiocontext = (GstMatroskaTrackAudioContext *) context;
406 for (; caps != NULL; caps = caps->next) {
407 mimetype = gst_caps_get_mime (caps);
412 "channels", &channels,
414 audiocontext->samplerate = samplerate;
415 audiocontext->channels = channels;
416 audiocontext->bitdepth = 16;
418 if (!strcmp (mimetype, "audio/mpeg")) {
419 gint mpegversion = 0;
421 gst_caps_get_int (caps, "mpegversion", &mpegversion);
422 switch (mpegversion) {
426 gst_caps_get_int (caps, "layer", &layer);
429 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1);
432 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2);
435 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3);
441 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG2
445 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG4
450 return GST_PAD_LINK_OK;
451 } else if (!strcmp (mimetype, "audio/x-raw-int")) {
452 gint endianness, width, depth;
456 "endianness", &endianness,
459 "signed", &signedness,
461 if (width != depth ||
462 (width == 8 && signedness) || (width == 16 && !signedness))
465 audiocontext->bitdepth = depth;
466 if (endianness == G_BIG_ENDIAN)
467 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE);
469 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE);
471 return GST_PAD_LINK_OK;
472 } else if (!strcmp (mimetype, "audio/x-raw-float")) {
473 /* FIXME: endianness is undefined */
474 } else if (!strcmp (mimetype, "audio/x-vorbis")) {
475 /* FIXME: private data setup needs work */
476 } else if (!strcmp (mimetype, "audio/x-ac3")) {
477 context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3);
479 return GST_PAD_LINK_OK;
483 return GST_PAD_LINK_REFUSED;
486 static GstPadLinkReturn
487 gst_matroska_mux_subtitle_pad_link (GstPad *pad,
490 /* Consider this as boilerplate code for now. There is
491 * no single subtitle creation element in GStreamer,
492 * neither do I know how subtitling works at all. */
494 return GST_PAD_LINK_REFUSED;
498 gst_matroska_mux_request_new_pad (GstElement *element,
499 GstPadTemplate *templ,
500 const gchar *pad_name)
502 GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
503 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
506 GstPadLinkFunction linkfunc = NULL;
507 GstMatroskaTrackContext *context = NULL;
509 if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
510 name = g_strdup_printf ("audio_%d", mux->num_a_streams++);
511 linkfunc = gst_matroska_mux_audio_pad_link;
512 context = (GstMatroskaTrackContext *)
513 g_new0 (GstMatroskaTrackAudioContext, 1);
514 context->type = GST_MATROSKA_TRACK_TYPE_AUDIO;
515 context->name = g_strdup ("Audio");
516 } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
517 name = g_strdup_printf ("video_%d", mux->num_v_streams++);
518 linkfunc = gst_matroska_mux_video_pad_link;
519 context = (GstMatroskaTrackContext *)
520 g_new0 (GstMatroskaTrackVideoContext, 1);
521 context->type = GST_MATROSKA_TRACK_TYPE_VIDEO;
522 context->name = g_strdup ("Video");
523 } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%d")) {
524 name = g_strdup_printf ("subtitle_%d", mux->num_t_streams++);
525 linkfunc = gst_matroska_mux_subtitle_pad_link;
526 context = (GstMatroskaTrackContext *)
527 g_new0 (GstMatroskaTrackSubtitleContext, 1);
528 context->type = GST_MATROSKA_TRACK_TYPE_SUBTITLE;
529 context->name = g_strdup ("Subtitle");
531 g_warning ("matroskamux: this is not our template!");
535 pad = gst_pad_new_from_template (templ, name);
537 gst_element_add_pad (element, pad);
538 gst_pad_set_link_function (pad, linkfunc);
539 context->index = mux->num_streams++;
540 mux->sink[context->index].track = context;
542 context->flags = GST_MATROSKA_TRACK_ENABLED |
543 GST_MATROSKA_TRACK_DEFAULT;
549 gst_matroska_mux_track_header (GstMatroskaMux *mux,
550 GstMatroskaTrackContext *context)
552 GstEbmlWrite *ebml = GST_EBML_WRITE (mux);
555 /* track type goes before the type-specific stuff */
556 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKNUMBER, context->num);
557 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKTYPE, context->type);
559 /* type-specific stuff */
560 switch (context->type) {
561 case GST_MATROSKA_TRACK_TYPE_VIDEO: {
562 GstMatroskaTrackVideoContext *videocontext =
563 (GstMatroskaTrackVideoContext *) context;
565 /* framerate, but not in the video part */
566 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TRACKDEFAULTDURATION,
567 context->default_duration);
569 master = gst_ebml_write_master_start (ebml,
570 GST_MATROSKA_ID_TRACKVIDEO);
571 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELWIDTH,
572 videocontext->pixel_width);
573 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOPIXELHEIGHT,
574 videocontext->pixel_height);
575 if (videocontext->display_width && videocontext->display_height) {
576 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYWIDTH,
577 videocontext->display_width);
578 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEODISPLAYHEIGHT,
579 videocontext->display_height);
581 if (context->flags & GST_MATROSKA_VIDEOTRACK_INTERLACED)
582 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_VIDEOFLAGINTERLACED, 1);
583 if (videocontext->fourcc) {
584 guint32 fcc_le = GUINT32_TO_LE (videocontext->fourcc);
585 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_VIDEOCOLOURSPACE,
586 (gpointer) &fcc_le, 4);
588 gst_ebml_write_master_finish (ebml, master);
593 case GST_MATROSKA_TRACK_TYPE_AUDIO: {
594 GstMatroskaTrackAudioContext *audiocontext =
595 (GstMatroskaTrackAudioContext *) context;
597 master = gst_ebml_write_master_start (ebml,
598 GST_MATROSKA_ID_TRACKAUDIO);
599 if (audiocontext->samplerate != 8000)
600 gst_ebml_write_float (ebml, GST_MATROSKA_ID_AUDIOSAMPLINGFREQ,
601 audiocontext->samplerate);
602 if (audiocontext->channels != 1)
603 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOCHANNELS,
604 audiocontext->channels);
605 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_AUDIOBITDEPTH,
606 audiocontext->bitdepth);
607 gst_ebml_write_master_finish (ebml, master);
613 /* doesn't need type-specific data */
617 gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CODECID,
619 if (context->codec_priv)
620 gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECPRIVATE,
621 context->codec_priv, context->codec_priv_size);
622 /* FIXME: until we have a nice way of getting the codecname
623 * out of the caps, I'm not going to enable this. Too much
624 * (useless, double, boring) work... */
625 /*gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CODECNAME,
626 context->codec_name);*/
627 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKNAME,
632 gst_matroska_mux_start (GstMatroskaMux *mux)
634 GstEbmlWrite *ebml = GST_EBML_WRITE (mux);
635 guint32 seekhead_id[] = { GST_MATROSKA_ID_INFO,
636 GST_MATROSKA_ID_TRACKS,
637 GST_MATROSKA_ID_CUES,
639 GST_MATROSKA_ID_TAGS,
642 guint64 master, child;
646 /* we start with a EBML header */
647 gst_ebml_write_header (ebml, "matroska", 1);
649 /* start a segment */
650 mux->segment_pos = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENT);
651 mux->segment_master = ebml->pos;
653 /* the rest of the header is cached */
654 gst_ebml_write_set_cache (ebml, 0x1000);
656 /* seekhead (table of contents) - we set the positions later */
657 mux->seekhead_pos = ebml->pos;
658 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD);
659 for (i = 0; seekhead_id[i] != 0; i++) {
660 child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKENTRY);
661 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, seekhead_id[i]);
662 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION, -1);
663 gst_ebml_write_master_finish (ebml, child);
665 gst_ebml_write_master_finish (ebml, master);
668 mux->info_pos = ebml->pos;
669 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_INFO);
670 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TIMECODESCALE, mux->time_scale);
671 mux->duration_pos = ebml->pos;
672 gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION, 0);
673 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_MUXINGAPP, "GStreamer");
675 gst_caps_has_property (mux->metadata, "application")) {
678 gst_caps_get_string (mux->metadata, "application", &app);
680 gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_WRITINGAPP, app);
683 /* FIXME: how do I get this? Automatic? Via tags? */
684 /*gst_ebml_write_date (ebml, GST_MATROSKA_ID_DATEUTC, 0);*/
685 gst_ebml_write_master_finish (ebml, master);
688 mux->tracks_pos = ebml->pos;
689 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKS);
690 for (i = 0; i < mux->num_streams; i++) {
691 if (GST_PAD_IS_USABLE (mux->sink[i].track->pad)) {
692 mux->sink[i].track->num = tracknum++;
693 child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKENTRY);
694 gst_matroska_mux_track_header (mux, mux->sink[i].track);
695 gst_ebml_write_master_finish (ebml, child);
698 gst_ebml_write_master_finish (ebml, master);
700 /* lastly, flush the cache */
701 gst_ebml_write_flush_cache (ebml);
705 gst_matroska_mux_finish (GstMatroskaMux *mux)
707 GstEbmlWrite *ebml = GST_EBML_WRITE (mux);
711 if (mux->index != NULL) {
713 guint64 master, pointentry_master, trackpos_master;
715 mux->cues_pos = ebml->pos;
716 gst_ebml_write_set_cache (ebml, 12 + 41 * mux->num_indexes);
717 master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CUES);
719 for (n = 0; n < mux->num_indexes; n++) {
720 GstMatroskaIndex *idx = &mux->index[n];
722 pointentry_master = gst_ebml_write_master_start (ebml,
723 GST_MATROSKA_ID_POINTENTRY);
724 gst_ebml_write_date (ebml, GST_MATROSKA_ID_CUETIME,
725 idx->time / mux->time_scale);
726 trackpos_master = gst_ebml_write_master_start (ebml,
727 GST_MATROSKA_ID_CUETRACKPOSITION);
728 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUETRACK, idx->track);
729 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CUECLUSTERPOSITION,
730 idx->pos - mux->segment_master);
731 gst_ebml_write_master_finish (ebml, trackpos_master);
732 gst_ebml_write_master_finish (ebml, pointentry_master);
735 gst_ebml_write_master_finish (ebml, master);
736 gst_ebml_write_flush_cache (ebml);
741 /* update seekhead. We know that:
742 * - a seekhead contains 4 entries.
743 * - order of entries is as above.
744 * - a seekhead has a 4-byte header + 8-byte length
745 * - each entry is 2-byte master, 2-byte ID pointer,
746 * 2-byte length pointer, all 8/1-byte length, 4-
747 * byte ID and 8-byte length pointer, where the
748 * length pointer starts at 20.
749 * - all entries are local to the segment (so pos - segment_master).
750 * - so each entry is at 12 + 20 + num * 28. */
751 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 32,
752 mux->info_pos - mux->segment_master);
753 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 60,
754 mux->tracks_pos - mux->segment_master);
755 if (mux->index != NULL) {
756 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88,
757 mux->cues_pos - mux->segment_master);
760 guint64 my_pos = ebml->pos;
761 gst_ebml_write_seek (ebml, mux->seekhead_pos + 68);
762 gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
763 gst_ebml_write_seek (ebml, my_pos);
766 gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116,
767 mux->tags_pos - mux->segment_master);
770 /* update duration */
771 pos = GST_EBML_WRITE (mux)->pos;
772 gst_ebml_write_seek (ebml, mux->duration_pos);
773 gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
774 mux->duration / mux->time_scale);
775 gst_ebml_write_seek (ebml, pos);
777 /* finish segment - this also writes element length */
778 gst_ebml_write_master_finish (ebml, mux->segment_pos);
782 gst_matroska_mux_prepare_data (GstMatroskaMux *mux)
786 for (i = 0; i < mux->num_streams; i++) {
787 while (!mux->sink[i].eos && !mux->sink[i].buffer &&
788 mux->sink[i].track->num > 0 &&
789 GST_PAD_IS_USABLE (mux->sink[i].track->pad)) {
792 data = gst_pad_pull (mux->sink[i].track->pad);
793 if (GST_IS_EVENT (data)) {
794 if (GST_EVENT_TYPE (GST_EVENT (data)) == GST_EVENT_EOS)
795 mux->sink[i].eos = TRUE;
796 gst_event_unref (GST_EVENT (data));
798 mux->sink[i].buffer = GST_BUFFER (data);
802 if (mux->sink[i].buffer) {
803 if (first < 0 || GST_BUFFER_TIMESTAMP (mux->sink[i].buffer) <
804 GST_BUFFER_TIMESTAMP (mux->sink[first].buffer))
813 gst_matroska_mux_write_data (GstMatroskaMux *mux)
815 GstEbmlWrite *ebml = GST_EBML_WRITE (mux);
816 GstBuffer *buf, *hdr;
818 guint64 cluster, blockgroup;
820 /* which stream-num to write from? */
821 if ((i = gst_matroska_mux_prepare_data (mux)) < 0) {
822 GstEvent *event = gst_event_new (GST_EVENT_EOS);
824 gst_matroska_mux_finish (mux);
825 gst_pad_push (mux->srcpad, GST_DATA (event));
826 gst_element_set_eos (GST_ELEMENT (mux));
832 buf = mux->sink[i].buffer;
833 mux->sink[i].buffer = NULL;
835 /* We currently write an index entry for each keyframe in a
836 * video track. This can be largely improved, such as doing
837 * one for each keyframe or each second (for all-keyframe
838 * streams), only the *first* video track or the audio track
839 * if we have no video tracks. But that'll come later... */
840 if (mux->sink[i].track->type == GST_MATROSKA_TRACK_TYPE_VIDEO &&
841 GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_KEY_UNIT)) {
842 GstMatroskaIndex *idx;
844 if (mux->num_indexes % 32 == 0) {
845 mux->index = g_renew (GstMatroskaIndex, mux->index,
846 mux->num_indexes + 32);
848 idx = &mux->index[mux->num_indexes++];
850 idx->pos = ebml->pos;
851 idx->time = GST_BUFFER_TIMESTAMP (buf);
852 idx->track = mux->sink[i].track->num;
855 /* write one cluster with one blockgroup with one block with
856 * one slice (*breath*).
857 * FIXME: lacing, multiple frames/cluster, etc. */
858 cluster = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
859 gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
860 GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
861 blockgroup = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_BLOCKGROUP);
862 gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_BLOCK,
863 GST_BUFFER_SIZE (buf) + 4);
864 hdr = gst_buffer_new_and_alloc (4);
865 /* track num - FIXME: what if num >= 0x80 (unlikely)? */
866 GST_BUFFER_DATA (hdr)[0] = mux->sink[i].track->num | 0x80;
867 /* time relative to clustertime - we don't use this yet */
868 * (guint16 *) &GST_BUFFER_DATA (hdr)[1] = GUINT16_TO_BE (0);
869 /* flags - no lacing (yet) */
870 GST_BUFFER_DATA (hdr)[3] = 0;
871 gst_ebml_write_buffer (ebml, hdr);
872 gst_ebml_write_buffer (ebml, buf);
873 gst_ebml_write_master_finish (ebml, blockgroup);
874 gst_ebml_write_master_finish (ebml, cluster);
878 gst_matroska_mux_loop (GstElement *element)
880 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
882 /* start with a header */
883 if (mux->state == GST_MATROSKA_MUX_STATE_START) {
884 mux->state = GST_MATROSKA_MUX_STATE_HEADER;
885 gst_matroska_mux_start (mux);
886 mux->state = GST_MATROSKA_MUX_STATE_DATA;
889 /* do one single buffer */
890 gst_matroska_mux_write_data (mux);
893 static GstElementStateReturn
894 gst_matroska_mux_change_state (GstElement *element)
896 GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
898 switch (GST_STATE_TRANSITION (element)) {
899 case GST_STATE_PAUSED_TO_READY:
900 gst_matroska_mux_reset (GST_ELEMENT (mux));
906 if (((GstElementClass *) parent_class)->change_state)
907 return ((GstElementClass *) parent_class)->change_state (element);
909 return GST_STATE_SUCCESS;
913 gst_matroska_mux_set_property (GObject *object,
920 g_return_if_fail (GST_IS_MATROSKA_MUX (object));
921 mux = GST_MATROSKA_MUX (object);
925 gst_caps_replace (&mux->metadata,
926 g_value_get_boxed (value));
929 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
935 gst_matroska_mux_get_property (GObject *object,
942 g_return_if_fail (GST_IS_MATROSKA_MUX (object));
943 mux = GST_MATROSKA_MUX (object);
947 g_value_set_boxed (value, mux->metadata);
950 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
956 gst_matroska_mux_plugin_init (GstPlugin *plugin)
958 return gst_element_register (plugin, "matroskamux",
960 GST_TYPE_MATROSKA_MUX);