2a6b792dfbb039333816e5f422d5206ed0d1d613
[platform/upstream/gst-plugins-good.git] / gst / quicktime / gstqtmux.c
1 /* Quicktime muxer plugin for GStreamer
2  * Copyright (C) 2008 Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>
3  * Copyright (C) 2008 Mark Nauwelaerts <mnauw@users.sf.net>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21
22 /**
23  * SECTION:gstqtmux
24  * @short_description: Muxer for quicktime(.mov) files
25  *
26  * <refsect2>
27  * <para>
28  * This element merges streams (audio and video) into qt(.mov) files.
29  * </para>
30  * <title>Example pipelines</title>
31  * <para>
32  * <programlisting>
33  * gst-launch v4l2src num-buffers=500 ! video/x-raw-yuv,width=320,height=240 ! ffmpegcolorspace ! qtmux ! filesink location=video.mov
34  * </programlisting>
35  * Records a video stream captured from a v4l2 device and muxes it into a qt file.
36  * </para>
37  * </refsect2>
38  *
39  * Last reviewed on 2008-08-27
40  */
41
42 /*
43  * Based on avimux
44  */
45
46 #ifdef HAVE_CONFIG_H
47 #include "config.h"
48 #endif
49
50 #include <glib/gstdio.h>
51
52 #include <gst/gst.h>
53 #include <gst/base/gstcollectpads.h>
54
55 #include "gstqtmux.h"
56
57 GST_DEBUG_CATEGORY_STATIC (gst_qt_mux_debug);
58 #define GST_CAT_DEFAULT gst_qt_mux_debug
59
60 /* QTMux signals and args */
61 enum
62 {
63   /* FILL ME */
64   LAST_SIGNAL
65 };
66
67 enum
68 {
69   PROP_0,
70   PROP_LARGE_FILE,
71   PROP_MOVIE_TIMESCALE,
72   PROP_DO_CTTS,
73   PROP_FLAVOR,
74   PROP_FAST_START,
75   PROP_FAST_START_TEMP_FILE
76 };
77
78 #define MDAT_ATOM_HEADER_SIZE           16
79 #define DEFAULT_LARGE_FILE              FALSE
80 #define DEFAULT_MOVIE_TIMESCALE         600
81 #define DEFAULT_DO_CTTS                 FALSE
82 #define DEFAULT_FAST_START              FALSE
83 #define DEFAULT_FAST_START_TEMP_FILE    NULL
84
85 static void gst_qt_mux_finalize (GObject * object);
86
87 static GstStateChangeReturn gst_qt_mux_change_state (GstElement * element,
88     GstStateChange transition);
89
90 /* property functions */
91 static void gst_qt_mux_set_property (GObject * object,
92     guint prop_id, const GValue * value, GParamSpec * pspec);
93 static void gst_qt_mux_get_property (GObject * object,
94     guint prop_id, GValue * value, GParamSpec * pspec);
95
96 /* pad functions */
97 static GstPad *gst_qt_mux_request_new_pad (GstElement * element,
98     GstPadTemplate * templ, const gchar * name);
99 static void gst_qt_mux_release_pad (GstElement * element, GstPad * pad);
100
101 /* event */
102 static gboolean gst_qt_mux_sink_event (GstPad * pad, GstEvent * event);
103
104 static GstFlowReturn gst_qt_mux_collected (GstCollectPads * pads,
105     gpointer user_data);
106 static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
107     GstBuffer * buf);
108
109 static GstElementClass *parent_class = NULL;
110
111 static void
112 gst_qt_mux_base_init (gpointer g_class)
113 {
114   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
115   GstQTMuxClass *klass = (GstQTMuxClass *) g_class;
116   GstQTMuxClassParams *params;
117   GstElementDetails details;
118   GstPadTemplate *videosinktempl, *audiosinktempl, *srctempl;
119
120   params =
121       (GstQTMuxClassParams *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (g_class),
122       GST_QT_MUX_PARAMS_QDATA);
123   g_assert (params != NULL);
124
125   /* construct the element details struct */
126   details.longname = g_strdup_printf ("%s Muxer", params->prop->long_name);
127   details.klass = g_strdup ("Codec/Muxer");
128   details.description =
129       g_strdup_printf ("Multiplex audio and video into a %s file",
130       params->prop->long_name);
131   details.author = "Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>";
132   gst_element_class_set_details (element_class, &details);
133   g_free (details.longname);
134   g_free (details.klass);
135   g_free (details.description);
136
137   /* pad templates */
138   srctempl = gst_pad_template_new ("src", GST_PAD_SRC,
139       GST_PAD_ALWAYS, params->src_caps);
140   gst_element_class_add_pad_template (element_class, srctempl);
141
142   if (params->audio_sink_caps) {
143     audiosinktempl = gst_pad_template_new ("audio_%d",
144         GST_PAD_SINK, GST_PAD_REQUEST, params->audio_sink_caps);
145     gst_element_class_add_pad_template (element_class, audiosinktempl);
146   }
147
148   if (params->video_sink_caps) {
149     videosinktempl = gst_pad_template_new ("video_%d",
150         GST_PAD_SINK, GST_PAD_REQUEST, params->video_sink_caps);
151     gst_element_class_add_pad_template (element_class, videosinktempl);
152   }
153
154   klass->format = params->prop->format;
155 }
156
157 static void
158 gst_qt_mux_class_init (GstQTMuxClass * klass)
159 {
160   GObjectClass *gobject_class;
161   GstElementClass *gstelement_class;
162
163   gobject_class = (GObjectClass *) klass;
164   gstelement_class = (GstElementClass *) klass;
165
166   parent_class = g_type_class_peek_parent (klass);
167
168   gobject_class->finalize = gst_qt_mux_finalize;
169   gobject_class->get_property = gst_qt_mux_get_property;
170   gobject_class->set_property = gst_qt_mux_set_property;
171
172   g_object_class_install_property (gobject_class, PROP_LARGE_FILE,
173       g_param_spec_boolean ("large-file", "Support for large files",
174           "Uses 64bits to some fields instead of 32bits, "
175           "providing support for large files",
176           DEFAULT_LARGE_FILE, G_PARAM_READWRITE));
177   g_object_class_install_property (gobject_class, PROP_MOVIE_TIMESCALE,
178       g_param_spec_uint ("movie-timescale", "Movie timescale",
179           "Timescale to use in the movie (units per second)",
180           1, G_MAXUINT32, DEFAULT_MOVIE_TIMESCALE,
181           G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
182   g_object_class_install_property (gobject_class, PROP_DO_CTTS,
183       g_param_spec_boolean ("presentation-time",
184           "Include presentation-time info",
185           "Calculate and include presentation/composition time (in addition to decoding time)"
186           " (use with caution)", DEFAULT_DO_CTTS,
187           G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
188   g_object_class_install_property (gobject_class, PROP_FAST_START,
189       g_param_spec_boolean ("faststart", "Format file to faststart",
190           "If the file should be formated for faststart (headers first). ",
191           DEFAULT_FAST_START, G_PARAM_READWRITE));
192   g_object_class_install_property (gobject_class, PROP_FAST_START_TEMP_FILE,
193       g_param_spec_string ("faststart-file", "File to use for storing buffers",
194           "File that will be used temporarily to store data from the stream when "
195           "creating a faststart file. If null a filepath will be created automatically",
196           DEFAULT_FAST_START_TEMP_FILE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
197
198   gstelement_class->request_new_pad =
199       GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
200   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qt_mux_change_state);
201   gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_release_pad);
202 }
203
204 static void
205 gst_qt_mux_pad_reset (GstQTPad * qtpad)
206 {
207   qtpad->fourcc = 0;
208   qtpad->is_out_of_order = FALSE;
209   qtpad->have_dts = FALSE;
210   qtpad->sample_size = 0;
211   qtpad->sync = FALSE;
212   qtpad->last_dts = 0;
213
214   if (qtpad->last_buf)
215     gst_buffer_replace (&qtpad->last_buf, NULL);
216
217   /* reference owned elsewhere */
218   qtpad->trak = NULL;
219 }
220
221 /*
222  * Takes GstQTMux back to its initial state
223  */
224 static void
225 gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
226 {
227   GSList *walk;
228
229   qtmux->state = GST_QT_MUX_STATE_NONE;
230   qtmux->header_size = 0;
231   qtmux->mdat_size = 0;
232   qtmux->mdat_pos = 0;
233
234   if (qtmux->ftyp) {
235     atom_ftyp_free (qtmux->ftyp);
236     qtmux->ftyp = NULL;
237   }
238   if (qtmux->moov) {
239     atom_moov_free (qtmux->moov);
240     qtmux->moov = NULL;
241   }
242   if (qtmux->tags) {
243     gst_tag_list_free (qtmux->tags);
244     qtmux->tags = NULL;
245   }
246   if (qtmux->fast_start_file) {
247     fclose (qtmux->fast_start_file);
248     qtmux->fast_start_file = NULL;
249   }
250
251   /* reset pad data */
252   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
253     GstQTPad *qtpad = (GstQTPad *) walk->data;
254     gst_qt_mux_pad_reset (qtpad);
255
256     /* hm, moov_free above yanked the traks away from us,
257      * so do not free, but do clear */
258     qtpad->trak = NULL;
259   }
260
261   if (alloc) {
262     qtmux->moov = atom_moov_new (qtmux->context);
263   }
264 }
265
266 static void
267 gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
268 {
269   GstElementClass *klass = GST_ELEMENT_CLASS (qtmux_klass);
270   GstPadTemplate *templ;
271   GstCaps *caps;
272
273   templ = gst_element_class_get_pad_template (klass, "src");
274   qtmux->srcpad = gst_pad_new_from_template (templ, "src");
275   caps = gst_caps_copy (gst_pad_get_pad_template_caps (qtmux->srcpad));
276   gst_pad_set_caps (qtmux->srcpad, caps);
277   gst_caps_unref (caps);
278   gst_pad_use_fixed_caps (qtmux->srcpad);
279   gst_element_add_pad (GST_ELEMENT (qtmux), qtmux->srcpad);
280
281   qtmux->collect = gst_collect_pads_new ();
282   gst_collect_pads_set_function (qtmux->collect,
283       (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_qt_mux_collected), qtmux);
284
285   /* properties set to default upon construction */
286
287   /* always need this */
288   qtmux->context =
289       atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format));
290
291   /* internals to initial state */
292   gst_qt_mux_reset (qtmux, TRUE);
293 }
294
295
296 static void
297 gst_qt_mux_finalize (GObject * object)
298 {
299   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
300
301   gst_qt_mux_reset (qtmux, FALSE);
302
303   if (qtmux->fast_start_file_path)
304     g_free (qtmux->fast_start_file_path);
305
306   atoms_context_free (qtmux->context);
307   gst_object_unref (qtmux->collect);
308
309   G_OBJECT_CLASS (parent_class)->finalize (object);
310 }
311
312 /* FIXME approach below is pretty Apple/MOV/MP4/iTunes specific,
313  * and as such does not comply with e.g. 3GPP specs */
314
315 /*
316  * Struct to record mappings from gstreamer tags to fourcc codes
317  */
318 typedef struct _GstTagToFourcc
319 {
320   guint32 fourcc;
321   const gchar *gsttag;
322   const gchar *gsttag2;
323 } GstTagToFourcc;
324
325 /* tag list tags to fourcc matching */
326 static const GstTagToFourcc tag_matches[] = {
327   {FOURCC__alb, GST_TAG_ALBUM,},
328   {FOURCC__ART, GST_TAG_ARTIST,},
329   {FOURCC__cmt, GST_TAG_COMMENT,},
330   {FOURCC__wrt, GST_TAG_COMPOSER,},
331   {FOURCC__gen, GST_TAG_GENRE,},
332   {FOURCC__nam, GST_TAG_TITLE,},
333   {FOURCC__des, GST_TAG_DESCRIPTION,},
334   {FOURCC__too, GST_TAG_ENCODER,},
335   {FOURCC_cprt, GST_TAG_COPYRIGHT,},
336   {FOURCC_keyw, GST_TAG_KEYWORDS,},
337   {FOURCC__day, GST_TAG_DATE,},
338   {FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE,},
339   {FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT},
340   {FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT},
341   {FOURCC_covr, GST_TAG_PREVIEW_IMAGE,},
342   {0, NULL,}
343 };
344
345 /* qtdemux produces these for atoms it cannot parse */
346 #define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag"
347
348 static void
349 gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list)
350 {
351   guint32 fourcc;
352   gint i;
353   const gchar *tag, *tag2;
354
355   for (i = 0; tag_matches[i].fourcc; i++) {
356     fourcc = tag_matches[i].fourcc;
357     tag = tag_matches[i].gsttag;
358     tag2 = tag_matches[i].gsttag2;
359
360     switch (gst_tag_get_type (tag)) {
361         /* strings */
362       case G_TYPE_STRING:
363       {
364         gchar *str = NULL;
365
366         if (!gst_tag_list_get_string (list, tag, &str) || !str)
367           break;
368         GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
369             GST_FOURCC_ARGS (fourcc), str);
370         atom_moov_add_str_tag (qtmux->moov, fourcc, str);
371         g_free (str);
372         break;
373       }
374         /* double */
375       case G_TYPE_DOUBLE:
376       {
377         gdouble value;
378
379         if (!gst_tag_list_get_double (list, tag, &value))
380           break;
381         GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
382             GST_FOURCC_ARGS (fourcc), (gint) value);
383         atom_moov_add_uint_tag (qtmux->moov, fourcc, 21, (gint) value);
384         break;
385       }
386         /* paired unsigned integers */
387       case G_TYPE_UINT:
388       {
389         guint value;
390         guint count;
391
392         if (!gst_tag_list_get_uint (list, tag, &value) ||
393             !gst_tag_list_get_uint (list, tag2, &count))
394           break;
395         GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u",
396             GST_FOURCC_ARGS (fourcc), value, count);
397         atom_moov_add_uint_tag (qtmux->moov, fourcc, 0,
398             value << 16 | (count & 0xFFFF));
399         break;
400       }
401       default:
402       {
403         if (gst_tag_get_type (tag) == GST_TYPE_DATE) {
404           GDate *date = NULL;
405           GDateYear year;
406           GDateMonth month;
407           GDateDay day;
408           gchar *str;
409
410           if (!gst_tag_list_get_date (list, tag, &date) || !date)
411             break;
412           year = g_date_get_year (date);
413           month = g_date_get_month (date);
414           day = g_date_get_day (date);
415
416           if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH &&
417               day == G_DATE_BAD_DAY) {
418             GST_WARNING_OBJECT (qtmux, "invalid date in tag");
419             break;
420           }
421
422           str = g_strdup_printf ("%u-%u-%u", year, month, day);
423           GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
424               GST_FOURCC_ARGS (fourcc), str);
425           atom_moov_add_str_tag (qtmux->moov, fourcc, str);
426         } else if (gst_tag_get_type (tag) == GST_TYPE_BUFFER) {
427           GValue value = { 0, };
428           GstBuffer *buf;
429           GstCaps *caps;
430           GstStructure *structure;
431           gint flags = 0;
432
433           if (!gst_tag_list_copy_value (&value, list, tag))
434             break;
435
436           buf = gst_value_get_buffer (&value);
437           if (!buf)
438             goto done;
439
440           caps = gst_buffer_get_caps (buf);
441           if (!caps) {
442             GST_WARNING_OBJECT (qtmux, "preview image without caps");
443             goto done;
444           }
445
446           GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);
447
448           structure = gst_caps_get_structure (caps, 0);
449           if (gst_structure_has_name (structure, "image/jpeg"))
450             flags = 13;
451           else if (gst_structure_has_name (structure, "image/png"))
452             flags = 14;
453           gst_caps_unref (caps);
454
455           if (!flags) {
456             GST_WARNING_OBJECT (qtmux, "preview image format not supported");
457             goto done;
458           }
459
460           GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT
461               " -> image size %d", GST_FOURCC_ARGS (fourcc),
462               GST_BUFFER_SIZE (buf));
463           atom_moov_add_tag (qtmux->moov, fourcc, flags, GST_BUFFER_DATA (buf),
464               GST_BUFFER_SIZE (buf));
465         done:
466           g_value_unset (&value);
467         } else
468           g_assert_not_reached ();
469         break;
470       }
471     }
472   }
473
474   /* add unparsed blobs if present */
475   if (gst_tag_exists (GST_QT_DEMUX_PRIVATE_TAG)) {
476     guint num_tags;
477
478     num_tags = gst_tag_list_get_tag_size (list, GST_QT_DEMUX_PRIVATE_TAG);
479     for (i = 0; i < num_tags; ++i) {
480       const GValue *val;
481       GstBuffer *buf;
482       GstCaps *caps = NULL;
483
484       val = gst_tag_list_get_value_index (list, GST_QT_DEMUX_PRIVATE_TAG, i);
485       buf = (GstBuffer *) gst_value_get_mini_object (val);
486
487       if (buf && (caps = gst_buffer_get_caps (buf))) {
488         GstStructure *s;
489         const gchar *style = NULL;
490
491         GST_DEBUG_OBJECT (qtmux, "Found private tag %d/%d; size %d, caps %"
492             GST_PTR_FORMAT, i, num_tags, GST_BUFFER_SIZE (buf), caps);
493         s = gst_caps_get_structure (caps, 0);
494         if (s && (style = gst_structure_get_string (s, "style"))) {
495           /* FIXME make into a parameter */
496           if (strcmp (style, "itunes") == 0) {
497             GST_DEBUG_OBJECT (qtmux, "Adding private tag");
498             atom_moov_add_blob_tag (qtmux->moov, GST_BUFFER_DATA (buf),
499                 GST_BUFFER_SIZE (buf));
500           }
501         }
502         gst_caps_unref (caps);
503       }
504     }
505   }
506
507   return;
508 }
509
510 /*
511  * Gets the tagsetter iface taglist and puts the known tags
512  * into the output stream
513  */
514 static void
515 gst_qt_mux_setup_metadata (GstQTMux * qtmux)
516 {
517   const GstTagList *user_tags;
518   GstTagList *mixedtags = NULL;
519   GstTagMergeMode merge_mode;
520
521   user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (qtmux));
522   merge_mode = gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (qtmux));
523
524   GST_DEBUG_OBJECT (qtmux, "merging tags, merge mode = %d", merge_mode);
525   GST_LOG_OBJECT (qtmux, "event tags: %" GST_PTR_FORMAT, qtmux->tags);
526   GST_LOG_OBJECT (qtmux, "set   tags: %" GST_PTR_FORMAT, user_tags);
527
528   mixedtags = gst_tag_list_merge (user_tags, qtmux->tags, merge_mode);
529
530   GST_LOG_OBJECT (qtmux, "final tags: %" GST_PTR_FORMAT, mixedtags);
531
532   if (mixedtags && !gst_tag_list_is_empty (mixedtags)) {
533     GST_DEBUG_OBJECT (qtmux, "Parsing tags");
534     gst_qt_mux_add_metadata_tags (qtmux, mixedtags);
535   } else {
536     GST_DEBUG_OBJECT (qtmux, "No tags found");
537   }
538
539   if (mixedtags)
540     gst_tag_list_free (mixedtags);
541
542   return;
543 }
544
545 static GstFlowReturn
546 gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset,
547     gboolean mind_fast)
548 {
549   GstFlowReturn res;
550   guint8 *data;
551   guint size;
552
553   g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
554
555   data = GST_BUFFER_DATA (buf);
556   size = GST_BUFFER_SIZE (buf);
557
558   GST_LOG_OBJECT (qtmux, "sending buffer size %d", size);
559
560   if (mind_fast && qtmux->fast_start_file) {
561     gint ret;
562
563     GST_LOG_OBJECT (qtmux, "to temporary file");
564     ret = fwrite (data, sizeof (guint8), size, qtmux->fast_start_file);
565     gst_buffer_unref (buf);
566     if (ret != size)
567       goto write_error;
568     else
569       res = GST_FLOW_OK;
570   } else {
571     GST_LOG_OBJECT (qtmux, "downstream");
572
573     gst_buffer_set_caps (buf, GST_PAD_CAPS (qtmux->srcpad));
574     res = gst_pad_push (qtmux->srcpad, buf);
575   }
576
577   if (offset)
578     *offset += size;
579
580   return res;
581
582   /* ERRORS */
583 write_error:
584   {
585     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
586         ("Failed to write to temporary file"), GST_ERROR_SYSTEM);
587     return GST_FLOW_ERROR;
588   }
589 }
590
591 static GstFlowReturn
592 gst_qt_mux_send_buffered_data (GstQTMux * qtmux, guint64 * offset)
593 {
594   GstFlowReturn ret = GST_FLOW_OK;
595   GstBuffer *buf = NULL;
596
597   if (fflush (qtmux->fast_start_file))
598     goto flush_failed;
599
600   if (fseek (qtmux->fast_start_file, 0, SEEK_SET))
601     goto seek_failed;
602
603   /* hm, this could all take a really really long time,
604    * but there may not be another way to get moov atom first
605    * (somehow optimize copy?) */
606   GST_DEBUG_OBJECT (qtmux, "Sending buffered data");
607   while (ret == GST_FLOW_OK) {
608     gint r;
609     const int bufsize = 4096;
610
611     buf = gst_buffer_new_and_alloc (bufsize);
612     r = fread (GST_BUFFER_DATA (buf), sizeof (guint8), bufsize,
613         qtmux->fast_start_file);
614     if (r == 0)
615       break;
616     GST_BUFFER_SIZE (buf) = r;
617     GST_LOG_OBJECT (qtmux, "Pushing buffered buffer of size %d", r);
618     ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
619     buf = NULL;
620   }
621   if (buf)
622     gst_buffer_unref (buf);
623
624 exit:
625   /* best cleaning up effort, eat possible error */
626   fclose (qtmux->fast_start_file);
627   qtmux->fast_start_file = NULL;
628
629   /* FIXME maybe delete temporary file, or let the system handle that ? */
630
631   return ret;
632
633   /* ERRORS */
634 flush_failed:
635   {
636     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
637         ("Failed to flush temporary file"), GST_ERROR_SYSTEM);
638     ret = GST_FLOW_ERROR;
639     goto exit;
640   }
641 seek_failed:
642   {
643     GST_ELEMENT_ERROR (qtmux, RESOURCE, SEEK,
644         ("Failed to seek temporary file"), GST_ERROR_SYSTEM);
645     ret = GST_FLOW_ERROR;
646     goto exit;
647   }
648 }
649
650 /*
651  * Sends the initial mdat atom fields (size fields and fourcc type),
652  * the subsequent buffers are considered part of it's data.
653  * As we can't predict the amount of data that we are going to place in mdat
654  * we need to record the position of the size field in the stream so we can
655  * seek back to it later and update when the streams have finished.
656  */
657 static GstFlowReturn
658 gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size)
659 {
660   Atom *node_header;
661   GstBuffer *buf;
662   guint8 *data = NULL;
663   guint64 offset = 0;
664
665   GST_DEBUG_OBJECT (qtmux, "Sending mdat's atom header, "
666       "size %" G_GUINT64_FORMAT, size);
667
668   node_header = g_malloc0 (sizeof (Atom));
669   node_header->type = FOURCC_mdat;
670   /* use extended size */
671   node_header->size = 1;
672   node_header->extended_size = 0;
673   if (size)
674     node_header->extended_size = size;
675
676   size = offset = 0;
677   if (atom_copy_data (node_header, &data, &size, &offset) == 0)
678     goto serialize_error;
679
680   buf = gst_buffer_new ();
681   GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
682   GST_BUFFER_SIZE (buf) = offset;
683
684   g_free (node_header);
685
686   GST_LOG_OBJECT (qtmux, "Pushing mdat start");
687   return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
688
689   /* ERRORS */
690 serialize_error:
691   {
692     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
693         ("Failed to serialize ftyp"));
694     return GST_FLOW_ERROR;
695   }
696 }
697
698 /*
699  * We get the position of the mdat size field, seek back to it
700  * and overwrite with the real value
701  */
702 static GstFlowReturn
703 gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos,
704     guint64 mdat_size, guint64 * offset)
705 {
706   GstEvent *event;
707   GstBuffer *buf;
708
709   /* seek and rewrite the header */
710   event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES,
711       mdat_pos, GST_CLOCK_TIME_NONE, 0);
712   gst_pad_push_event (qtmux->srcpad, event);
713
714   buf = gst_buffer_new_and_alloc (sizeof (guint64));
715   GST_WRITE_UINT64_BE (GST_BUFFER_DATA (buf), mdat_size);
716
717   return gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
718 }
719
720 static GstFlowReturn
721 gst_qt_mux_stop_file (GstQTMux * qtmux)
722 {
723   gboolean ret = GST_FLOW_OK;
724   GstBuffer *buffer = NULL;
725   guint64 offset = 0, size = 0;
726   guint8 *data;
727   GSList *walk;
728   gboolean large_file;
729   guint32 timescale;
730
731   GST_DEBUG_OBJECT (qtmux, "Updating remaining values and sending last data");
732
733   /* pushing last buffers for each pad */
734   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
735     GstCollectData *cdata = (GstCollectData *) walk->data;
736     GstQTPad *qtpad = (GstQTPad *) cdata;
737
738     /* send last buffer */
739     GST_DEBUG_OBJECT (qtmux, "Sending the last buffer for pad %s",
740         GST_PAD_NAME (qtpad->collect.pad));
741     ret = gst_qt_mux_add_buffer (qtmux, qtpad, NULL);
742     if (ret != GST_FLOW_OK)
743       GST_DEBUG_OBJECT (qtmux, "Failed to send last buffer for %s, "
744           "flow return: %s", GST_PAD_NAME (qtpad->collect.pad),
745           gst_flow_get_name (ret));
746   }
747
748   GST_OBJECT_LOCK (qtmux);
749   timescale = qtmux->timescale;
750   large_file = qtmux->large_file;
751   GST_OBJECT_UNLOCK (qtmux);
752
753   /* inform lower layers of our property wishes, and determine duration.
754    * Let moov take care of this using its list of traks;
755    * so that released pads are also included */
756   GST_DEBUG_OBJECT (qtmux, "Large file support: %d", large_file);
757   GST_DEBUG_OBJECT (qtmux, "Updating timescale to %" G_GUINT32_FORMAT,
758       timescale);
759   atom_moov_update_timescale (qtmux->moov, timescale);
760   atom_moov_set_64bits (qtmux->moov, large_file);
761   atom_moov_update_duration (qtmux->moov);
762
763   /* tags into file metadata */
764   gst_qt_mux_setup_metadata (qtmux);
765
766   /* if faststart, update the offset of the atoms in the movie with the offset
767    * that the movie headers before mdat will cause */
768   if (qtmux->fast_start_file) {
769     /* copy into NULL to obtain size */
770     offset = size = 0;
771     if (!atom_moov_copy_data (qtmux->moov, NULL, &size, &offset))
772       goto serialize_error;
773     GST_DEBUG_OBJECT (qtmux, "calculated moov atom size %" G_GUINT64_FORMAT,
774         size);
775     offset += qtmux->header_size + MDAT_ATOM_HEADER_SIZE;
776   } else
777     offset = qtmux->header_size;
778   atom_moov_chunks_add_offset (qtmux->moov, offset);
779
780   /* serialize moov */
781   offset = size = 0;
782   data = NULL;
783   GST_LOG_OBJECT (qtmux, "Copying movie header into buffer");
784   ret = atom_moov_copy_data (qtmux->moov, &data, &size, &offset);
785   if (!ret)
786     goto serialize_error;
787
788   buffer = gst_buffer_new ();
789   GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer) = data;
790   GST_BUFFER_SIZE (buffer) = offset;
791   /* note: as of this point, we no longer care about tracking written data size,
792    * since there is no more use for it anyway */
793   GST_DEBUG_OBJECT (qtmux, "Pushing movie atoms");
794   gst_qt_mux_send_buffer (qtmux, buffer, NULL, FALSE);
795
796   /* total mdat size as of now also includes the atom header */
797   qtmux->mdat_size += MDAT_ATOM_HEADER_SIZE;
798   /* if needed, send mdat atom and move buffered data into it */
799   if (qtmux->fast_start_file) {
800     /* mdat size = accumulated (buffered data) + mdat atom header */
801     ret = gst_qt_mux_send_mdat_header (qtmux, NULL, qtmux->mdat_size);
802     if (ret != GST_FLOW_OK)
803       return ret;
804     ret = gst_qt_mux_send_buffered_data (qtmux, NULL);
805     if (ret != GST_FLOW_OK)
806       return ret;
807   } else {
808     /* mdata needs update iff not using faststart */
809     GST_DEBUG_OBJECT (qtmux, "updating mdata size");
810     ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
811         qtmux->mdat_size, NULL);
812     /* note; no seeking back to the end of file is done,
813      * since we longer write anything anyway */
814   }
815
816   return ret;
817
818   /* ERRORS */
819 serialize_error:
820   {
821     gst_buffer_unref (buffer);
822     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
823         ("Failed to serialize moov"));
824     return GST_FLOW_ERROR;
825   }
826 }
827
828 static GstFlowReturn
829 gst_qt_mux_send_ftyp (GstQTMux * qtmux, guint64 * off)
830 {
831   GstBuffer *buf;
832   guint64 size = 0, offset = 0;
833   guint8 *data = NULL;
834
835   GST_DEBUG_OBJECT (qtmux, "Sending ftyp atom");
836
837   if (!atom_ftyp_copy_data (qtmux->ftyp, &data, &size, &offset))
838     goto serialize_error;
839
840   buf = gst_buffer_new ();
841   GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
842   GST_BUFFER_SIZE (buf) = offset;
843
844   GST_LOG_OBJECT (qtmux, "Pushing ftyp");
845   return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
846
847   /* ERRORS */
848 serialize_error:
849   {
850     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
851         ("Failed to serialize ftyp"));
852     return GST_FLOW_ERROR;
853   }
854 }
855
856 static GstFlowReturn
857 gst_qt_mux_start_file (GstQTMux * qtmux)
858 {
859   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
860   GstFlowReturn ret = GST_FLOW_OK;
861   guint32 major, version;
862   GList *comp;
863   GstBuffer *prefix;
864
865   GST_DEBUG_OBJECT (qtmux, "starting file");
866
867   /* let downstream know we think in BYTES and expect to do seeking later on */
868   gst_pad_push_event (qtmux->srcpad,
869       gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0));
870
871   /* init and send context and ftyp based on current property state */
872   if (qtmux->ftyp)
873     atom_ftyp_free (qtmux->ftyp);
874   gst_qt_mux_map_format_to_header (qtmux_klass->format, &prefix, &major,
875       &version, &comp, qtmux->moov);
876   qtmux->ftyp = atom_ftyp_new (qtmux->context, major, version, comp);
877   if (comp)
878     g_list_free (comp);
879   if (prefix) {
880     ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE);
881     if (ret != GST_FLOW_OK)
882       goto exit;
883   }
884   ret = gst_qt_mux_send_ftyp (qtmux, &qtmux->header_size);
885   if (ret != GST_FLOW_OK)
886     goto exit;
887
888   /* send mdat header if already needed, and mark position for later update */
889   GST_OBJECT_LOCK (qtmux);
890   if (qtmux->fast_start) {
891     qtmux->fast_start_file = g_fopen (qtmux->fast_start_file_path, "wb+");
892     if (!qtmux->fast_start_file)
893       goto open_failed;
894   } else {
895     ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0);
896     /* mdat size position = current header pos - extended header size */
897     qtmux->mdat_pos = qtmux->header_size - sizeof (guint64);
898   }
899   GST_OBJECT_UNLOCK (qtmux);
900
901 exit:
902   return ret;
903
904   /* ERRORS */
905 open_failed:
906   {
907     GST_ELEMENT_ERROR (qtmux, RESOURCE, OPEN_READ_WRITE,
908         (("Could not open temporary file \"%s\""), qtmux->fast_start_file_path),
909         GST_ERROR_SYSTEM);
910     GST_OBJECT_UNLOCK (qtmux);
911     return GST_FLOW_ERROR;
912   }
913 }
914
915 /*
916  * Here we push the buffer and update the tables in the track atoms
917  */
918 static GstFlowReturn
919 gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
920 {
921   GstBuffer *last_buf = NULL;
922   GstClockTime duration;
923   guint nsamples, sample_size;
924   guint64 scaled_duration, chunk_offset;
925   gint64 last_dts;
926   gint64 pts_offset = 0;
927   gboolean sync = FALSE, do_pts = FALSE;
928
929   if (!pad->fourcc)
930     goto not_negotiated;
931
932   last_buf = pad->last_buf;
933   if (last_buf == NULL) {
934 #ifndef GST_DISABLE_GST_DEBUG
935     if (buf == NULL) {
936       GST_DEBUG_OBJECT (qtmux, "Pad %s has no previous buffer stored and "
937           "received NULL buffer, doing nothing",
938           GST_PAD_NAME (pad->collect.pad));
939     } else {
940       GST_LOG_OBJECT (qtmux,
941           "Pad %s has no previous buffer stored, storing now",
942           GST_PAD_NAME (pad->collect.pad));
943     }
944 #endif
945     pad->last_buf = buf;
946     return GST_FLOW_OK;
947   } else
948     gst_buffer_ref (last_buf);
949
950   /* fall back to duration if:
951    * - last bufer
952    * - this format has out of order buffers (e.g. MPEG-4),
953    * - lack of valid time forces fall back */
954   if (buf == NULL || pad->is_out_of_order ||
955       !GST_BUFFER_TIMESTAMP_IS_VALID (last_buf) ||
956       !GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
957     if (!GST_BUFFER_DURATION_IS_VALID (last_buf)) {
958       /* be forgiving for some possibly last upstream flushed buffer */
959       if (buf)
960         goto no_time;
961       GST_WARNING_OBJECT (qtmux, "no duration for last buffer");
962       /* iso spec recommends some small value, try 0 */
963       duration = 0;
964     } else {
965       duration = GST_BUFFER_DURATION (last_buf);
966     }
967   } else {
968     duration = GST_BUFFER_TIMESTAMP (buf) - GST_BUFFER_TIMESTAMP (last_buf);
969   }
970
971   gst_buffer_replace (&pad->last_buf, buf);
972
973   last_dts = gst_util_uint64_scale (pad->last_dts,
974       atom_trak_get_timescale (pad->trak), GST_SECOND);
975
976   /* raw audio has many samples per buffer (= chunk) */
977   if (pad->sample_size) {
978     sample_size = pad->sample_size;
979     if (GST_BUFFER_SIZE (last_buf) % sample_size != 0)
980       goto fragmented_sample;
981     /* note: qt raw audio storage warps it implicitly into a timewise
982      * perfect stream, discarding buffer times */
983     nsamples = GST_BUFFER_SIZE (last_buf) / sample_size;
984     duration = GST_BUFFER_DURATION (last_buf) / nsamples;
985     /* timescale = samplerate */
986     scaled_duration = 1;
987     pad->last_dts += duration * nsamples;
988   } else {
989     nsamples = 1;
990     sample_size = GST_BUFFER_SIZE (last_buf);
991     if (pad->have_dts) {
992       gint64 scaled_dts;
993       pad->last_dts = GST_BUFFER_OFFSET_END (last_buf);
994       if ((gint64) (pad->last_dts) < 0) {
995         scaled_dts = -gst_util_uint64_scale (-pad->last_dts,
996             atom_trak_get_timescale (pad->trak), GST_SECOND);
997       } else {
998         scaled_dts = gst_util_uint64_scale (pad->last_dts,
999             atom_trak_get_timescale (pad->trak), GST_SECOND);
1000       }
1001       scaled_duration = scaled_dts - last_dts;
1002       last_dts = scaled_dts;
1003     } else {
1004       /* first convert intended timestamp (in GstClockTime resolution) to
1005        * trak timescale, then derive delta;
1006        * this ensures sums of (scale)delta add up to converted timestamp,
1007        * which only deviates at most 1/scale from timestamp itself */
1008       scaled_duration = gst_util_uint64_scale (pad->last_dts + duration,
1009           atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts;
1010       pad->last_dts += duration;
1011     }
1012   }
1013   chunk_offset = qtmux->mdat_size;
1014
1015   GST_LOG_OBJECT (qtmux,
1016       "Pad (%s) dts updated to %" GST_TIME_FORMAT,
1017       GST_PAD_NAME (pad->collect.pad), GST_TIME_ARGS (pad->last_dts));
1018   GST_LOG_OBJECT (qtmux,
1019       "Adding %d samples to track, duration: %" G_GUINT64_FORMAT
1020       " size: %" G_GUINT32_FORMAT " chunk offset: %" G_GUINT64_FORMAT,
1021       nsamples, scaled_duration, sample_size, chunk_offset);
1022
1023   /* might be a sync sample */
1024   if (pad->sync &&
1025       !GST_BUFFER_FLAG_IS_SET (last_buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
1026     GST_LOG_OBJECT (qtmux, "Adding new sync sample entry for track of pad %s",
1027         GST_PAD_NAME (pad->collect.pad));
1028     sync = TRUE;
1029   }
1030
1031   /* optionally calculate ctts entry values
1032    * (if composition-time expected different from decoding-time) */
1033   /* really not recommended:
1034    * - decoder typically takes care of dts/pts issues
1035    * - in case of out-of-order, dts may only be determined as above
1036    *   (e.g. sum of duration), which may be totally different from
1037    *   buffer timestamps in case of multiple segment, non-perfect streams
1038    *  (and just perhaps maybe with some luck segment_to_running_time
1039    *   or segment_to_media_time might get near to it) */
1040   if ((pad->have_dts || qtmux->guess_pts) && pad->is_out_of_order) {
1041     guint64 pts;
1042
1043     pts = gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (last_buf),
1044         atom_trak_get_timescale (pad->trak), GST_SECOND);
1045     pts_offset = (gint64) (pts - last_dts);
1046     do_pts = TRUE;
1047     GST_ERROR_OBJECT (qtmux, "Adding ctts entry for pad %s: %" G_GINT64_FORMAT,
1048         GST_PAD_NAME (pad->collect.pad), pts_offset);
1049   }
1050
1051   /* now we go and register this buffer/sample all over */
1052   /* note that a new chunk is started each time (not fancy but works) */
1053   atom_trak_add_samples (pad->trak, nsamples, scaled_duration, sample_size,
1054       chunk_offset, sync, do_pts, pts_offset);
1055
1056   if (buf)
1057     gst_buffer_unref (buf);
1058
1059   return gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE);
1060
1061   /* ERRORS */
1062 bail:
1063   {
1064     if (buf)
1065       gst_buffer_unref (buf);
1066     gst_buffer_unref (last_buf);
1067     return GST_FLOW_ERROR;
1068   }
1069 no_time:
1070   {
1071     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1072         ("Failed to determine time to mux."));
1073     goto bail;
1074   }
1075 fragmented_sample:
1076   {
1077     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1078         ("Audio buffer contains fragmented sample."));
1079     goto bail;
1080   }
1081 not_negotiated:
1082   {
1083     GST_ELEMENT_ERROR (qtmux, CORE, NEGOTIATION, (NULL),
1084         ("format wasn't negotiated before buffer flow on pad %s",
1085             GST_PAD_NAME (pad->collect.pad)));
1086     gst_buffer_unref (buf);
1087     return GST_FLOW_NOT_NEGOTIATED;
1088   }
1089 }
1090
1091 static GstFlowReturn
1092 gst_qt_mux_collected (GstCollectPads * pads, gpointer user_data)
1093 {
1094   GstFlowReturn ret = GST_FLOW_OK;
1095   GstQTMux *qtmux = GST_QT_MUX_CAST (user_data);
1096   GSList *walk;
1097   GstQTPad *best_pad = NULL;
1098   GstClockTime time, best_time = GST_CLOCK_TIME_NONE;
1099   GstBuffer *buf;
1100
1101   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_STARTED)) {
1102     if ((ret = gst_qt_mux_start_file (qtmux)) != GST_FLOW_OK)
1103       return ret;
1104     else
1105       qtmux->state = GST_QT_MUX_STATE_DATA;
1106   }
1107
1108   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS))
1109     return GST_FLOW_UNEXPECTED;
1110
1111   /* select the best buffer */
1112   walk = qtmux->collect->data;
1113   while (walk) {
1114     GstQTPad *pad;
1115     GstCollectData *data;
1116
1117     data = (GstCollectData *) walk->data;
1118     pad = (GstQTPad *) data;
1119
1120     walk = g_slist_next (walk);
1121
1122     buf = gst_collect_pads_peek (pads, data);
1123     if (buf == NULL) {
1124       GST_LOG_OBJECT (qtmux, "Pad %s has no buffers",
1125           GST_PAD_NAME (pad->collect.pad));
1126       continue;
1127     }
1128     time = GST_BUFFER_TIMESTAMP (buf);
1129     gst_buffer_unref (buf);
1130
1131     if (best_pad == NULL || !GST_CLOCK_TIME_IS_VALID (time) ||
1132         (GST_CLOCK_TIME_IS_VALID (best_time) && time < best_time)) {
1133       best_pad = pad;
1134       best_time = time;
1135     }
1136   }
1137
1138   if (best_pad != NULL) {
1139     GST_LOG_OBJECT (qtmux, "selected pad %s with time %" GST_TIME_FORMAT,
1140         GST_PAD_NAME (best_pad->collect.pad), GST_TIME_ARGS (best_time));
1141     buf = gst_collect_pads_pop (pads, &best_pad->collect);
1142     ret = gst_qt_mux_add_buffer (qtmux, best_pad, buf);
1143   } else {
1144     ret = gst_qt_mux_stop_file (qtmux);
1145     if (ret == GST_FLOW_OK) {
1146       gst_pad_push_event (qtmux->srcpad, gst_event_new_eos ());
1147       ret = GST_FLOW_UNEXPECTED;
1148     }
1149     qtmux->state = GST_QT_MUX_STATE_EOS;
1150   }
1151
1152   return ret;
1153 }
1154
1155 static gboolean
1156 gst_qt_mux_audio_sink_set_caps (GstPad * pad, GstCaps * caps)
1157 {
1158   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1159   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1160   GstQTPad *qtpad = NULL;
1161   GstStructure *structure;
1162   const gchar *mimetype;
1163   gint rate, channels;
1164   const GValue *value = NULL;
1165   const GstBuffer *codec_data = NULL;
1166   GstQTMuxFormat format;
1167   AudioSampleEntry entry = { 0, };
1168   AtomInfo *ext_atom = NULL;
1169   gint constant_size = 0;
1170
1171   /* find stream data */
1172   qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
1173   g_assert (qtpad);
1174
1175   /* does not go well to renegotiate stream mid-way */
1176   if (qtpad->fourcc)
1177     goto refuse_renegotiation;
1178
1179   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
1180       GST_DEBUG_PAD_NAME (pad), caps);
1181
1182   format = qtmux_klass->format;
1183   structure = gst_caps_get_structure (caps, 0);
1184   mimetype = gst_structure_get_name (structure);
1185
1186   /* common info */
1187   if (!gst_structure_get_int (structure, "channels", &channels) ||
1188       !gst_structure_get_int (structure, "rate", &rate)) {
1189     goto refuse_caps;
1190   }
1191
1192   /* optional */
1193   value = gst_structure_get_value (structure, "codec_data");
1194   if (value != NULL)
1195     codec_data = gst_value_get_buffer (value);
1196
1197   qtpad->is_out_of_order = FALSE;
1198   qtpad->have_dts = FALSE;
1199
1200   /* set common properties */
1201   entry.sample_rate = rate;
1202   entry.channels = channels;
1203   /* default */
1204   entry.sample_size = 16;
1205   /* this is the typical compressed case */
1206   if (format == GST_QT_MUX_FORMAT_QT) {
1207     entry.version = 1;
1208     entry.compression_id = -2;
1209   }
1210
1211   /* now map onto a fourcc, and some extra properties */
1212   if (strcmp (mimetype, "audio/mpeg") == 0) {
1213     gint mpegversion = 0;
1214     gint layer = -1;
1215
1216     gst_structure_get_int (structure, "mpegversion", &mpegversion);
1217     switch (mpegversion) {
1218       case 1:
1219         gst_structure_get_int (structure, "layer", &layer);
1220         switch (layer) {
1221           case 3:
1222             /* mp3 */
1223             /* note: QuickTime player does not like mp3 either way in iso/mp4 */
1224             if (format == GST_QT_MUX_FORMAT_QT)
1225               entry.fourcc = FOURCC__mp3;
1226             else {
1227               entry.fourcc = FOURCC_mp4a;
1228               ext_atom =
1229                   build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG1_P3,
1230                   ESDS_STREAM_TYPE_AUDIO, codec_data);
1231             }
1232             entry.samples_per_packet = 1152;
1233             entry.bytes_per_sample = 2;
1234             break;
1235         }
1236         break;
1237       case 4:
1238         /* AAC */
1239         entry.fourcc = FOURCC_mp4a;
1240         if (!codec_data || GST_BUFFER_SIZE (codec_data) < 2)
1241           GST_WARNING_OBJECT (qtmux, "no (valid) codec_data for AAC audio");
1242         else {
1243           guint8 profile = GST_READ_UINT8 (GST_BUFFER_DATA (codec_data));
1244
1245           /* warn if not Low Complexity profile */
1246           profile >>= 3;
1247           if (profile != 2)
1248             GST_WARNING_OBJECT (qtmux,
1249                 "non-LC AAC may not run well on (Apple) QuickTime/iTunes");
1250         }
1251         if (format == GST_QT_MUX_FORMAT_QT)
1252           ext_atom = build_mov_aac_extension (qtpad->trak, codec_data);
1253         else
1254           ext_atom =
1255               build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P3,
1256               ESDS_STREAM_TYPE_AUDIO, codec_data);
1257         break;
1258       default:
1259         break;
1260     }
1261   } else if (strcmp (mimetype, "audio/AMR") == 0) {
1262     entry.fourcc = FOURCC_samr;
1263     entry.sample_size = 16;
1264     entry.samples_per_packet = 160;
1265     entry.bytes_per_sample = 2;
1266   } else if (strcmp (mimetype, "audio/x-raw-int") == 0) {
1267     gint width;
1268     gint depth;
1269     gint endianness;
1270     gboolean sign;
1271
1272     if (!gst_structure_get_int (structure, "width", &width) ||
1273         !gst_structure_get_int (structure, "depth", &depth) ||
1274         !gst_structure_get_boolean (structure, "signed", &sign) ||
1275         !gst_structure_get_int (structure, "endianness", &endianness)) {
1276       GST_DEBUG_OBJECT (qtmux,
1277           "broken caps, width/depth/signed/endianness field missing");
1278       goto refuse_caps;
1279     }
1280
1281     /* spec has no place for a distinction in these */
1282     if (width != depth) {
1283       GST_DEBUG_OBJECT (qtmux, "width must be same as depth!");
1284       goto refuse_caps;
1285     }
1286
1287     if (sign) {
1288       if (endianness == G_LITTLE_ENDIAN)
1289         entry.fourcc = FOURCC_sowt;
1290       else if (endianness == G_BIG_ENDIAN)
1291         entry.fourcc = FOURCC_twos;
1292       /* maximum backward compatibility; only new version for > 16 bit */
1293       if (depth <= 16)
1294         entry.version = 0;
1295       /* not compressed in any case */
1296       entry.compression_id = 0;
1297       /* QT spec says: max at 16 bit even if sample size were actually larger,
1298        * however, most players (e.g. QuickTime!) seem to disagree, so ... */
1299       entry.sample_size = depth;
1300       entry.bytes_per_sample = depth / 8;
1301       entry.samples_per_packet = 1;
1302       entry.bytes_per_packet = depth / 8;
1303       entry.bytes_per_frame = entry.bytes_per_packet * channels;
1304     } else {
1305       if (width == 8 && depth == 8) {
1306         /* fall back to old 8-bit version */
1307         entry.fourcc = FOURCC_raw_;
1308         entry.version = 0;
1309         entry.compression_id = 0;
1310         entry.sample_size = 8;
1311       } else {
1312         GST_DEBUG_OBJECT (qtmux, "non 8-bit PCM must be signed");
1313         goto refuse_caps;
1314       }
1315     }
1316     constant_size = (depth / 8) * channels;
1317   } else if (strcmp (mimetype, "audio/x-alaw") == 0) {
1318     entry.fourcc = FOURCC_alaw;
1319     entry.samples_per_packet = 1023;
1320     entry.bytes_per_sample = 2;
1321   } else if (strcmp (mimetype, "audio/x-mulaw") == 0) {
1322     entry.fourcc = FOURCC_ulaw;
1323     entry.samples_per_packet = 1023;
1324     entry.bytes_per_sample = 2;
1325   }
1326
1327   if (!entry.fourcc)
1328     goto refuse_caps;
1329
1330   /* ok, set the pad info accordingly */
1331   qtpad->fourcc = entry.fourcc;
1332   qtpad->sample_size = constant_size;
1333   atom_trak_set_audio_type (qtpad->trak, qtmux->context, &entry,
1334       entry.sample_rate, ext_atom, constant_size);
1335
1336   gst_object_unref (qtmux);
1337   return TRUE;
1338
1339   /* ERRORS */
1340 refuse_caps:
1341   {
1342     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
1343         GST_PAD_NAME (pad), caps);
1344     gst_object_unref (qtmux);
1345     return FALSE;
1346   }
1347 refuse_renegotiation:
1348   {
1349     GST_WARNING_OBJECT (qtmux,
1350         "pad %s refused renegotiation to %" GST_PTR_FORMAT,
1351         GST_PAD_NAME (pad), caps);
1352     gst_object_unref (qtmux);
1353     return FALSE;
1354   }
1355 }
1356
1357 /* scale rate up or down by factor of 10 to fit into [1000,10000] interval */
1358 static guint32
1359 adjust_rate (guint64 rate)
1360 {
1361   while (rate >= 10000)
1362     rate /= 10;
1363
1364   while (rate < 1000)
1365     rate *= 10;
1366
1367   return (guint32) rate;
1368 }
1369
1370 static gboolean
1371 gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps)
1372 {
1373   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1374   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1375   GstQTPad *qtpad = NULL;
1376   GstStructure *structure;
1377   const gchar *mimetype;
1378   gint width, height, depth = -1;
1379   gint framerate_num, framerate_den;
1380   guint32 rate;
1381   const GValue *value = NULL;
1382   const GstBuffer *codec_data = NULL;
1383   VisualSampleEntry entry = { 0, };
1384   GstQTMuxFormat format;
1385   AtomInfo *ext_atom = NULL;
1386   gboolean sync = FALSE;
1387
1388   /* find stream data */
1389   qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
1390   g_assert (qtpad);
1391
1392   /* does not go well to renegotiate stream mid-way */
1393   if (qtpad->fourcc)
1394     goto refuse_renegotiation;
1395
1396   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
1397       GST_DEBUG_PAD_NAME (pad), caps);
1398
1399   format = qtmux_klass->format;
1400   structure = gst_caps_get_structure (caps, 0);
1401   mimetype = gst_structure_get_name (structure);
1402
1403   /* required parts */
1404   if (!gst_structure_get_int (structure, "width", &width) ||
1405       !gst_structure_get_int (structure, "height", &height))
1406     goto refuse_caps;
1407
1408   /* optional */
1409   depth = -1;
1410   /* works as a default timebase */
1411   framerate_num = 10000;
1412   framerate_den = 1;
1413   gst_structure_get_fraction (structure, "framerate", &framerate_num,
1414       &framerate_den);
1415   gst_structure_get_int (structure, "depth", &depth);
1416   value = gst_structure_get_value (structure, "codec_data");
1417   if (value != NULL)
1418     codec_data = gst_value_get_buffer (value);
1419
1420   /* FIXME: pixel-aspect-ratio */
1421
1422   qtpad->is_out_of_order = FALSE;
1423
1424   /* bring frame numerator into a range that ensures both reasonable resolution
1425    * as well as a fair duration */
1426   rate = adjust_rate (framerate_num);
1427   GST_DEBUG_OBJECT (qtmux, "Rate of video track selected: %" G_GUINT32_FORMAT,
1428       rate);
1429
1430   /* set common properties */
1431   entry.width = width;
1432   entry.height = height;
1433   /* should be OK according to qt and iso spec, override if really needed */
1434   entry.color_table_id = -1;
1435   entry.frame_count = 1;
1436   entry.depth = 24;
1437
1438   /* sync entries by default */
1439   sync = TRUE;
1440
1441   /* now map onto a fourcc, and some extra properties */
1442   if (strcmp (mimetype, "video/x-raw-rgb") == 0) {
1443     gint bpp;
1444
1445     entry.fourcc = FOURCC_raw_;
1446     gst_structure_get_int (structure, "bpp", &bpp);
1447     entry.depth = bpp;
1448     sync = FALSE;
1449   } else if (strcmp (mimetype, "video/x-raw-yuv") == 0) {
1450     guint32 format = 0;
1451
1452     sync = FALSE;
1453     gst_structure_get_fourcc (structure, "format", &format);
1454     switch (format) {
1455       case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
1456         if (depth == -1)
1457           depth = 24;
1458         entry.fourcc = FOURCC_2vuy;
1459         entry.depth = depth;
1460         break;
1461     }
1462   } else if (strcmp (mimetype, "video/x-h263") == 0) {
1463     entry.fourcc = FOURCC_h263;
1464   } else if (strcmp (mimetype, "video/x-divx") == 0 ||
1465       strcmp (mimetype, "video/mpeg") == 0) {
1466     gint version = 0;
1467
1468     if (strcmp (mimetype, "video/x-divx") == 0) {
1469       gst_structure_get_int (structure, "divxversion", &version);
1470       version = version == 5 ? 1 : 0;
1471     } else {
1472       gst_structure_get_int (structure, "mpegversion", &version);
1473       version = version == 4 ? 1 : 0;
1474     }
1475     if (version) {
1476       entry.fourcc = FOURCC_mp4v;
1477       ext_atom =
1478           build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P2,
1479           ESDS_STREAM_TYPE_VISUAL, codec_data);
1480       if (!codec_data)
1481         GST_WARNING_OBJECT (qtmux, "no codec_data for MPEG4 video; "
1482             "output might not play in Apple QuickTime (try global-headers?)");
1483     }
1484   } else if (strcmp (mimetype, "video/x-h264") == 0) {
1485     entry.fourcc = FOURCC_avc1;
1486     qtpad->is_out_of_order = TRUE;
1487     if (!codec_data)
1488       GST_WARNING_OBJECT (qtmux, "no codec_data in h264 caps");
1489     ext_atom = build_codec_data_extension (FOURCC_avcC, codec_data);
1490   } else if (strcmp (mimetype, "video/x-dv") == 0) {
1491     gint version = 0;
1492     gboolean pal = TRUE;
1493
1494     sync = FALSE;
1495     if (framerate_num != 25 || framerate_den != 1)
1496       pal = FALSE;
1497     gst_structure_get_int (structure, "dvversion", &version);
1498     /* fall back to typical one */
1499     if (!version)
1500       version = 25;
1501     switch (version) {
1502       case 25:
1503         if (pal)
1504           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', 'p');
1505         else
1506           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', ' ');
1507         break;
1508       case 50:
1509         if (pal)
1510           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'p');
1511         else
1512           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'n');
1513         break;
1514       default:
1515         GST_WARNING_OBJECT (qtmux, "unrecognized dv version");
1516         break;
1517     }
1518   } else if (strcmp (mimetype, "image/jpeg") == 0) {
1519     entry.fourcc = FOURCC_jpeg;
1520     sync = FALSE;
1521   } else if (strcmp (mimetype, "image/x-j2c") == 0) {
1522     guint32 fourcc;
1523
1524     entry.fourcc = FOURCC_mjp2;
1525     sync = FALSE;
1526     if (!gst_structure_get_fourcc (structure, "fourcc", &fourcc) ||
1527         !(ext_atom =
1528             build_jp2h_extension (qtpad->trak, width, height, fourcc))) {
1529       GST_DEBUG_OBJECT (qtmux, "missing or invalid fourcc in jp2 caps");
1530       goto refuse_caps;
1531     }
1532   } else if (strcmp (mimetype, "video/x-qt-part") == 0) {
1533     guint32 fourcc;
1534
1535     gst_structure_get_fourcc (structure, "format", &fourcc);
1536     entry.fourcc = fourcc;
1537     qtpad->is_out_of_order = TRUE;
1538     qtpad->have_dts = TRUE;
1539   } else if (strcmp (mimetype, "video/x-mp4-part") == 0) {
1540     guint32 fourcc;
1541
1542     gst_structure_get_fourcc (structure, "format", &fourcc);
1543     entry.fourcc = fourcc;
1544     qtpad->is_out_of_order = TRUE;
1545     qtpad->have_dts = TRUE;
1546   }
1547
1548   if (!entry.fourcc)
1549     goto refuse_caps;
1550
1551   /* ok, set the pad info accordingly */
1552   qtpad->fourcc = entry.fourcc;
1553   qtpad->sync = sync;
1554   atom_trak_set_video_type (qtpad->trak, qtmux->context, &entry, rate,
1555       ext_atom);
1556
1557   gst_object_unref (qtmux);
1558   return TRUE;
1559
1560   /* ERRORS */
1561 refuse_caps:
1562   {
1563     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
1564         GST_PAD_NAME (pad), caps);
1565     gst_object_unref (qtmux);
1566     return FALSE;
1567   }
1568 refuse_renegotiation:
1569   {
1570     GST_WARNING_OBJECT (qtmux,
1571         "pad %s refused renegotiation to %" GST_PTR_FORMAT,
1572         GST_PAD_NAME (pad), caps);
1573     gst_object_unref (qtmux);
1574     return FALSE;
1575   }
1576 }
1577
1578 static gboolean
1579 gst_qt_mux_sink_event (GstPad * pad, GstEvent * event)
1580 {
1581   gboolean ret;
1582   GstQTMux *qtmux;
1583   GstTagList *list;
1584
1585   qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1586   switch (GST_EVENT_TYPE (event)) {
1587     case GST_EVENT_TAG:
1588       GST_DEBUG_OBJECT (qtmux, "received tag event");
1589       gst_event_parse_tag (event, &list);
1590
1591       if (qtmux->tags) {
1592         gst_tag_list_insert (qtmux->tags, list, GST_TAG_MERGE_PREPEND);
1593       } else {
1594         qtmux->tags = gst_tag_list_copy (list);
1595       }
1596       break;
1597     default:
1598       break;
1599   }
1600
1601   ret = qtmux->collect_event (pad, event);
1602   gst_object_unref (qtmux);
1603
1604   return ret;
1605 }
1606
1607 static void
1608 gst_qt_mux_release_pad (GstElement * element, GstPad * pad)
1609 {
1610   GstQTMux *mux = GST_QT_MUX_CAST (element);
1611
1612   /* let GstCollectPads complain if it is some unknown pad */
1613   if (gst_collect_pads_remove_pad (mux->collect, pad))
1614     gst_element_remove_pad (element, pad);
1615 }
1616
1617 static GstPad *
1618 gst_qt_mux_request_new_pad (GstElement * element,
1619     GstPadTemplate * templ, const gchar * name)
1620 {
1621   GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
1622   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
1623   GstQTPad *collect_pad;
1624   GstPad *newpad;
1625   gboolean audio;
1626
1627   GST_DEBUG_OBJECT (qtmux, "Requested pad: %s", GST_STR_NULL (name));
1628
1629   if (qtmux->state != GST_QT_MUX_STATE_NONE) {
1630     GST_WARNING_OBJECT (qtmux, "Not providing request pad after stream start.");
1631     return NULL;
1632   }
1633
1634   if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
1635     audio = TRUE;
1636   } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
1637     audio = FALSE;
1638   } else {
1639     GST_WARNING_OBJECT (qtmux, "This is not our template!");
1640     return NULL;
1641   }
1642
1643   /* add pad to collections */
1644   newpad = gst_pad_new_from_template (templ, name);
1645   collect_pad = (GstQTPad *)
1646       gst_collect_pads_add_pad_full (qtmux->collect, newpad, sizeof (GstQTPad),
1647       (GstCollectDataDestroyNotify) (gst_qt_mux_pad_reset));
1648   /* set up pad */
1649   gst_qt_mux_pad_reset (collect_pad);
1650   collect_pad->trak = atom_trak_new (qtmux->context);
1651   atom_moov_add_trak (qtmux->moov, collect_pad->trak);
1652
1653   /* set up pad functions */
1654   if (audio)
1655     gst_pad_set_setcaps_function (newpad,
1656         GST_DEBUG_FUNCPTR (gst_qt_mux_audio_sink_set_caps));
1657   else
1658     gst_pad_set_setcaps_function (newpad,
1659         GST_DEBUG_FUNCPTR (gst_qt_mux_video_sink_set_caps));
1660
1661   /* FIXME: hacked way to override/extend the event function of
1662    * GstCollectPads; because it sets its own event function giving the
1663    * element no access to events.
1664    */
1665   qtmux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
1666   gst_pad_set_event_function (newpad,
1667       GST_DEBUG_FUNCPTR (gst_qt_mux_sink_event));
1668
1669   gst_pad_set_active (newpad, TRUE);
1670   gst_element_add_pad (element, newpad);
1671
1672   return newpad;
1673 }
1674
1675 static void
1676 gst_qt_mux_get_property (GObject * object,
1677     guint prop_id, GValue * value, GParamSpec * pspec)
1678 {
1679   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
1680
1681   GST_OBJECT_LOCK (qtmux);
1682   switch (prop_id) {
1683     case PROP_LARGE_FILE:
1684       g_value_set_boolean (value, qtmux->large_file);
1685       break;
1686     case PROP_MOVIE_TIMESCALE:
1687       g_value_set_uint (value, qtmux->timescale);
1688       break;
1689     case PROP_DO_CTTS:
1690       g_value_set_boolean (value, qtmux->guess_pts);
1691       break;
1692     case PROP_FAST_START:
1693       g_value_set_boolean (value, qtmux->fast_start);
1694       break;
1695     case PROP_FAST_START_TEMP_FILE:
1696       g_value_set_string (value, qtmux->fast_start_file_path);
1697       break;
1698     default:
1699       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1700       break;
1701   }
1702   GST_OBJECT_UNLOCK (qtmux);
1703 }
1704
1705 static void
1706 gst_qt_mux_generate_fast_start_file_path (GstQTMux * qtmux)
1707 {
1708   gchar *tmp;
1709
1710   if (qtmux->fast_start_file_path) {
1711     g_free (qtmux->fast_start_file_path);
1712     qtmux->fast_start_file_path = NULL;
1713   }
1714
1715   tmp = g_strdup_printf ("%s%d", "qtmux", g_random_int ());
1716   qtmux->fast_start_file_path = g_build_filename (g_get_tmp_dir (), tmp, NULL);
1717   g_free (tmp);
1718 }
1719
1720 static void
1721 gst_qt_mux_set_property (GObject * object,
1722     guint prop_id, const GValue * value, GParamSpec * pspec)
1723 {
1724   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
1725
1726   GST_OBJECT_LOCK (qtmux);
1727   switch (prop_id) {
1728     case PROP_LARGE_FILE:
1729       qtmux->large_file = g_value_get_boolean (value);
1730       break;
1731     case PROP_MOVIE_TIMESCALE:
1732       qtmux->timescale = g_value_get_uint (value);
1733       break;
1734     case PROP_DO_CTTS:
1735       qtmux->guess_pts = g_value_get_boolean (value);
1736       break;
1737     case PROP_FAST_START:
1738       qtmux->fast_start = g_value_get_boolean (value);
1739       break;
1740     case PROP_FAST_START_TEMP_FILE:
1741       if (qtmux->fast_start_file_path) {
1742         g_free (qtmux->fast_start_file_path);
1743       }
1744       qtmux->fast_start_file_path = g_value_dup_string (value);
1745       /* NULL means to generate a random one */
1746       if (!qtmux->fast_start_file_path) {
1747         gst_qt_mux_generate_fast_start_file_path (qtmux);
1748       }
1749       break;
1750     default:
1751       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1752       break;
1753   }
1754   GST_OBJECT_UNLOCK (qtmux);
1755 }
1756
1757 static GstStateChangeReturn
1758 gst_qt_mux_change_state (GstElement * element, GstStateChange transition)
1759 {
1760   GstStateChangeReturn ret;
1761   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
1762
1763   switch (transition) {
1764     case GST_STATE_CHANGE_NULL_TO_READY:
1765       break;
1766     case GST_STATE_CHANGE_READY_TO_PAUSED:
1767       gst_collect_pads_start (qtmux->collect);
1768       qtmux->state = GST_QT_MUX_STATE_STARTED;
1769       break;
1770     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1771       break;
1772     case GST_STATE_CHANGE_PAUSED_TO_READY:
1773       gst_collect_pads_stop (qtmux->collect);
1774       break;
1775     default:
1776       break;
1777   }
1778
1779   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1780
1781   switch (transition) {
1782     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1783       break;
1784     case GST_STATE_CHANGE_PAUSED_TO_READY:
1785       gst_qt_mux_reset (qtmux, TRUE);
1786       break;
1787     case GST_STATE_CHANGE_READY_TO_NULL:
1788       break;
1789     default:
1790       break;
1791   }
1792
1793   return ret;
1794 }
1795
1796
1797 gboolean
1798 gst_qt_mux_register (GstPlugin * plugin)
1799 {
1800   GTypeInfo typeinfo = {
1801     sizeof (GstQTMuxClass),
1802     (GBaseInitFunc) gst_qt_mux_base_init,
1803     NULL,
1804     (GClassInitFunc) gst_qt_mux_class_init,
1805     NULL,
1806     NULL,
1807     sizeof (GstQTMux),
1808     0,
1809     (GInstanceInitFunc) gst_qt_mux_init,
1810   };
1811   static const GInterfaceInfo tag_setter_info = {
1812     NULL, NULL, NULL
1813   };
1814   GType type;
1815   GstQTMuxFormat format;
1816   GstQTMuxClassParams *params;
1817   guint i = 0;
1818
1819   GST_LOG ("Registering muxers");
1820
1821   while (TRUE) {
1822     GstQTMuxFormatProp *prop;
1823
1824     prop = &gst_qt_mux_format_list[i];
1825     format = prop->format;
1826     if (format == GST_QT_MUX_FORMAT_NONE)
1827       break;
1828
1829     /* create a cache for these properties */
1830     params = g_new0 (GstQTMuxClassParams, 1);
1831     params->prop = prop;
1832     params->src_caps = gst_static_caps_get (&prop->src_caps);
1833     params->video_sink_caps = gst_static_caps_get (&prop->video_sink_caps);
1834     params->audio_sink_caps = gst_static_caps_get (&prop->audio_sink_caps);
1835
1836     /* create the type now */
1837     type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo,
1838         0);
1839     g_type_set_qdata (type, GST_QT_MUX_PARAMS_QDATA, (gpointer) params);
1840     g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
1841
1842     if (!gst_element_register (plugin, prop->name, GST_RANK_NONE, type))
1843       return FALSE;
1844
1845     i++;
1846   }
1847
1848   GST_LOG ("Finished registering muxers");
1849
1850   return TRUE;
1851 }
1852
1853 gboolean
1854 gst_qt_mux_plugin_init (GstPlugin * plugin)
1855 {
1856   GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
1857
1858   return gst_qt_mux_register (plugin);
1859 }
1860
1861 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1862     GST_VERSION_MINOR,
1863     "qtmux",
1864     "Quicktime Muxer plugin",
1865     gst_qt_mux_plugin_init, VERSION, "LGPL", "gsoc2008 package",
1866     "embedded.ufcg.edu.br")