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