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