377871cc8a12ef9a796e54c269089eebff3f5806
[platform/upstream/gstreamer.git] / gst / quicktime / gstqtmux.c
1 /* Quicktime muxer plugin for GStreamer
2  * Copyright (C) 2008-2010 Thiago 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   PROP_MOOV_RECOV_FILE
113 };
114
115 /* some spare for header size as well */
116 #define MDAT_LARGE_FILE_LIMIT           ((guint64) 1024 * 1024 * 1024 * 2)
117 #define MAX_TOLERATED_LATENESS          (GST_SECOND / 10)
118
119 #define DEFAULT_LARGE_FILE              FALSE
120 #define DEFAULT_MOVIE_TIMESCALE         1000
121 #define DEFAULT_DO_CTTS                 FALSE
122 #define DEFAULT_FAST_START              FALSE
123 #define DEFAULT_FAST_START_TEMP_FILE    NULL
124 #define DEFAULT_MOOV_RECOV_FILE         NULL
125
126 static void gst_qt_mux_finalize (GObject * object);
127
128 static GstStateChangeReturn gst_qt_mux_change_state (GstElement * element,
129     GstStateChange transition);
130
131 /* property functions */
132 static void gst_qt_mux_set_property (GObject * object,
133     guint prop_id, const GValue * value, GParamSpec * pspec);
134 static void gst_qt_mux_get_property (GObject * object,
135     guint prop_id, GValue * value, GParamSpec * pspec);
136
137 /* pad functions */
138 static GstPad *gst_qt_mux_request_new_pad (GstElement * element,
139     GstPadTemplate * templ, const gchar * name);
140 static void gst_qt_mux_release_pad (GstElement * element, GstPad * pad);
141
142 /* event */
143 static gboolean gst_qt_mux_sink_event (GstPad * pad, GstEvent * event);
144
145 static GstFlowReturn gst_qt_mux_collected (GstCollectPads * pads,
146     gpointer user_data);
147 static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
148     GstBuffer * buf);
149
150 static GstElementClass *parent_class = NULL;
151
152 static void
153 gst_qt_mux_base_init (gpointer g_class)
154 {
155   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
156   GstQTMuxClass *klass = (GstQTMuxClass *) g_class;
157   GstQTMuxClassParams *params;
158   GstPadTemplate *videosinktempl, *audiosinktempl, *srctempl;
159   gchar *longname, *description;
160
161   params =
162       (GstQTMuxClassParams *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (g_class),
163       GST_QT_MUX_PARAMS_QDATA);
164   g_assert (params != NULL);
165
166   /* construct the element details struct */
167   longname = g_strdup_printf ("%s Muxer", params->prop->long_name);
168   description = g_strdup_printf ("Multiplex audio and video into a %s file",
169       params->prop->long_name);
170   gst_element_class_set_details_simple (element_class, longname,
171       "Codec/Muxer", description,
172       "Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>");
173   g_free (longname);
174   g_free (description);
175
176   /* pad templates */
177   srctempl = gst_pad_template_new ("src", GST_PAD_SRC,
178       GST_PAD_ALWAYS, params->src_caps);
179   gst_element_class_add_pad_template (element_class, srctempl);
180
181   if (params->audio_sink_caps) {
182     audiosinktempl = gst_pad_template_new ("audio_%d",
183         GST_PAD_SINK, GST_PAD_REQUEST, params->audio_sink_caps);
184     gst_element_class_add_pad_template (element_class, audiosinktempl);
185   }
186
187   if (params->video_sink_caps) {
188     videosinktempl = gst_pad_template_new ("video_%d",
189         GST_PAD_SINK, GST_PAD_REQUEST, params->video_sink_caps);
190     gst_element_class_add_pad_template (element_class, videosinktempl);
191   }
192
193   klass->format = params->prop->format;
194 }
195
196 static void
197 gst_qt_mux_class_init (GstQTMuxClass * klass)
198 {
199   GObjectClass *gobject_class;
200   GstElementClass *gstelement_class;
201
202   gobject_class = (GObjectClass *) klass;
203   gstelement_class = (GstElementClass *) klass;
204
205   parent_class = g_type_class_peek_parent (klass);
206
207   gobject_class->finalize = gst_qt_mux_finalize;
208   gobject_class->get_property = gst_qt_mux_get_property;
209   gobject_class->set_property = gst_qt_mux_set_property;
210
211   g_object_class_install_property (gobject_class, PROP_LARGE_FILE,
212       g_param_spec_boolean ("large-file", "Support for large files",
213           "Uses 64bits to some fields instead of 32bits, "
214           "providing support for large files",
215           DEFAULT_LARGE_FILE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
216   g_object_class_install_property (gobject_class, PROP_MOVIE_TIMESCALE,
217       g_param_spec_uint ("movie-timescale", "Movie timescale",
218           "Timescale to use in the movie (units per second)",
219           1, G_MAXUINT32, DEFAULT_MOVIE_TIMESCALE,
220           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
221   g_object_class_install_property (gobject_class, PROP_DO_CTTS,
222       g_param_spec_boolean ("presentation-time",
223           "Include presentation-time info",
224           "Calculate and include presentation/composition time "
225           "(in addition to decoding time) (use with caution)",
226           DEFAULT_DO_CTTS,
227           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
228   g_object_class_install_property (gobject_class, PROP_FAST_START,
229       g_param_spec_boolean ("faststart", "Format file to faststart",
230           "If the file should be formated for faststart (headers first). ",
231           DEFAULT_FAST_START, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
232   g_object_class_install_property (gobject_class, PROP_FAST_START_TEMP_FILE,
233       g_param_spec_string ("faststart-file", "File to use for storing buffers",
234           "File that will be used temporarily to store data from the stream "
235           "when creating a faststart file. If null a filepath will be "
236           "created automatically", DEFAULT_FAST_START_TEMP_FILE,
237           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
238   g_object_class_install_property (gobject_class, PROP_MOOV_RECOV_FILE,
239       g_param_spec_string ("moov-recovery-file",
240           "File to store data for posterior moov atom recovery",
241           "File to be used to store "
242           "data for moov atom making movie file recovery possible in case "
243           "of a crash during muxing. Null for disabled. (Experimental)",
244           DEFAULT_MOOV_RECOV_FILE,
245           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
246
247   gstelement_class->request_new_pad =
248       GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
249   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qt_mux_change_state);
250   gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_release_pad);
251 }
252
253 static void
254 gst_qt_mux_pad_reset (GstQTPad * qtpad)
255 {
256   qtpad->fourcc = 0;
257   qtpad->is_out_of_order = FALSE;
258   qtpad->have_dts = FALSE;
259   qtpad->sample_size = 0;
260   qtpad->sync = FALSE;
261   qtpad->last_dts = 0;
262   qtpad->first_ts = GST_CLOCK_TIME_NONE;
263   qtpad->prepare_buf_func = NULL;
264   qtpad->avg_bitrate = 0;
265   qtpad->max_bitrate = 0;
266
267   if (qtpad->last_buf)
268     gst_buffer_replace (&qtpad->last_buf, NULL);
269
270   /* reference owned elsewhere */
271   qtpad->trak = NULL;
272 }
273
274 /*
275  * Takes GstQTMux back to its initial state
276  */
277 static void
278 gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
279 {
280   GSList *walk;
281
282   qtmux->state = GST_QT_MUX_STATE_NONE;
283   qtmux->header_size = 0;
284   qtmux->mdat_size = 0;
285   qtmux->mdat_pos = 0;
286   qtmux->longest_chunk = GST_CLOCK_TIME_NONE;
287   qtmux->video_pads = 0;
288   qtmux->audio_pads = 0;
289
290   if (qtmux->ftyp) {
291     atom_ftyp_free (qtmux->ftyp);
292     qtmux->ftyp = NULL;
293   }
294   if (qtmux->moov) {
295     atom_moov_free (qtmux->moov);
296     qtmux->moov = NULL;
297   }
298   if (qtmux->fast_start_file) {
299     fclose (qtmux->fast_start_file);
300     g_remove (qtmux->fast_start_file_path);
301     qtmux->fast_start_file = NULL;
302   }
303   if (qtmux->moov_recov_file) {
304     fclose (qtmux->moov_recov_file);
305     qtmux->moov_recov_file = NULL;
306   }
307   for (walk = qtmux->extra_atoms; walk; walk = g_slist_next (walk)) {
308     AtomInfo *ainfo = (AtomInfo *) walk->data;
309     ainfo->free_func (ainfo->atom);
310   }
311   g_slist_free (qtmux->extra_atoms);
312   qtmux->extra_atoms = NULL;
313
314   GST_OBJECT_LOCK (qtmux);
315   gst_tag_setter_reset_tags (GST_TAG_SETTER (qtmux));
316   GST_OBJECT_UNLOCK (qtmux);
317
318   /* reset pad data */
319   for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
320     GstQTPad *qtpad = (GstQTPad *) walk->data;
321     gst_qt_mux_pad_reset (qtpad);
322
323     /* hm, moov_free above yanked the traks away from us,
324      * so do not free, but do clear */
325     qtpad->trak = NULL;
326   }
327
328   if (alloc) {
329     qtmux->moov = atom_moov_new (qtmux->context);
330     /* ensure all is as nice and fresh as request_new_pad would provide it */
331     for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
332       GstQTPad *qtpad = (GstQTPad *) walk->data;
333
334       qtpad->trak = atom_trak_new (qtmux->context);
335       atom_moov_add_trak (qtmux->moov, qtpad->trak);
336     }
337   }
338 }
339
340 static void
341 gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
342 {
343   GstElementClass *klass = GST_ELEMENT_CLASS (qtmux_klass);
344   GstPadTemplate *templ;
345   GstCaps *caps;
346
347   templ = gst_element_class_get_pad_template (klass, "src");
348   qtmux->srcpad = gst_pad_new_from_template (templ, "src");
349   caps = gst_caps_copy (gst_pad_get_pad_template_caps (qtmux->srcpad));
350   gst_pad_set_caps (qtmux->srcpad, caps);
351   gst_caps_unref (caps);
352   gst_pad_use_fixed_caps (qtmux->srcpad);
353   gst_element_add_pad (GST_ELEMENT (qtmux), qtmux->srcpad);
354
355   qtmux->sinkpads = NULL;
356   qtmux->collect = gst_collect_pads_new ();
357   gst_collect_pads_set_function (qtmux->collect,
358       (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_qt_mux_collected), qtmux);
359
360   /* properties set to default upon construction */
361
362   /* always need this */
363   qtmux->context =
364       atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format));
365
366   /* internals to initial state */
367   gst_qt_mux_reset (qtmux, TRUE);
368 }
369
370
371 static void
372 gst_qt_mux_finalize (GObject * object)
373 {
374   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
375
376   gst_qt_mux_reset (qtmux, FALSE);
377
378   g_free (qtmux->fast_start_file_path);
379   g_free (qtmux->moov_recov_file_path);
380
381   atoms_context_free (qtmux->context);
382   gst_object_unref (qtmux->collect);
383
384   G_OBJECT_CLASS (parent_class)->finalize (object);
385 }
386
387 static GstBuffer *
388 gst_qt_mux_prepare_jpc_buffer (GstQTPad * qtpad, GstBuffer * buf,
389     GstQTMux * qtmux)
390 {
391   GstBuffer *newbuf;
392
393   GST_LOG_OBJECT (qtmux, "Preparing jpc buffer");
394
395   if (buf == NULL)
396     return NULL;
397
398   newbuf = gst_buffer_new_and_alloc (GST_BUFFER_SIZE (buf) + 8);
399   gst_buffer_copy_metadata (newbuf, buf, GST_BUFFER_COPY_ALL);
400
401   GST_WRITE_UINT32_BE (GST_BUFFER_DATA (newbuf), GST_BUFFER_SIZE (newbuf));
402   GST_WRITE_UINT32_LE (GST_BUFFER_DATA (newbuf) + 4, FOURCC_jp2c);
403
404   memcpy (GST_BUFFER_DATA (newbuf) + 8, GST_BUFFER_DATA (buf),
405       GST_BUFFER_SIZE (buf));
406   gst_buffer_unref (buf);
407
408   return newbuf;
409 }
410
411 static void
412 gst_qt_mux_add_mp4_tag (GstQTMux * qtmux, const GstTagList * list,
413     const char *tag, const char *tag2, guint32 fourcc)
414 {
415   switch (gst_tag_get_type (tag)) {
416       /* strings */
417     case G_TYPE_STRING:
418     {
419       gchar *str = NULL;
420
421       if (!gst_tag_list_get_string (list, tag, &str) || !str)
422         break;
423       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
424           GST_FOURCC_ARGS (fourcc), str);
425       atom_moov_add_str_tag (qtmux->moov, fourcc, str);
426       g_free (str);
427       break;
428     }
429       /* double */
430     case G_TYPE_DOUBLE:
431     {
432       gdouble value;
433
434       if (!gst_tag_list_get_double (list, tag, &value))
435         break;
436       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
437           GST_FOURCC_ARGS (fourcc), (gint) value);
438       atom_moov_add_uint_tag (qtmux->moov, fourcc, 21, (gint) value);
439       break;
440     }
441     case G_TYPE_UINT:
442     {
443       guint value = 0;
444       if (tag2) {
445         /* paired unsigned integers */
446         guint count = 0;
447
448         if (!(gst_tag_list_get_uint (list, tag, &value) ||
449                 gst_tag_list_get_uint (list, tag2, &count)))
450           break;
451         GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u",
452             GST_FOURCC_ARGS (fourcc), value, count);
453         atom_moov_add_uint_tag (qtmux->moov, fourcc, 0,
454             value << 16 | (count & 0xFFFF));
455       } else {
456         /* unpaired unsigned integers */
457         if (!gst_tag_list_get_uint (list, tag, &value))
458           break;
459         GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
460             GST_FOURCC_ARGS (fourcc), value);
461         atom_moov_add_uint_tag (qtmux->moov, fourcc, 1, value);
462       }
463       break;
464     }
465     default:
466       g_assert_not_reached ();
467       break;
468   }
469 }
470
471 static void
472 gst_qt_mux_add_mp4_date (GstQTMux * qtmux, const GstTagList * list,
473     const char *tag, const char *tag2, guint32 fourcc)
474 {
475   GDate *date = NULL;
476   GDateYear year;
477   GDateMonth month;
478   GDateDay day;
479   gchar *str;
480
481   g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_DATE);
482
483   if (!gst_tag_list_get_date (list, tag, &date) || !date)
484     return;
485
486   year = g_date_get_year (date);
487   month = g_date_get_month (date);
488   day = g_date_get_day (date);
489
490   if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH &&
491       day == G_DATE_BAD_DAY) {
492     GST_WARNING_OBJECT (qtmux, "invalid date in tag");
493     return;
494   }
495
496   str = g_strdup_printf ("%u-%u-%u", year, month, day);
497   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
498       GST_FOURCC_ARGS (fourcc), str);
499   atom_moov_add_str_tag (qtmux->moov, fourcc, str);
500   g_free (str);
501 }
502
503 static void
504 gst_qt_mux_add_mp4_cover (GstQTMux * qtmux, const GstTagList * list,
505     const char *tag, const char *tag2, guint32 fourcc)
506 {
507   GValue value = { 0, };
508   GstBuffer *buf;
509   GstCaps *caps;
510   GstStructure *structure;
511   gint flags = 0;
512
513   g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_BUFFER);
514
515   if (!gst_tag_list_copy_value (&value, list, tag))
516     return;
517
518   buf = gst_value_get_buffer (&value);
519   if (!buf)
520     goto done;
521
522   caps = gst_buffer_get_caps (buf);
523   if (!caps) {
524     GST_WARNING_OBJECT (qtmux, "preview image without caps");
525     goto done;
526   }
527
528   GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);
529
530   structure = gst_caps_get_structure (caps, 0);
531   if (gst_structure_has_name (structure, "image/jpeg"))
532     flags = 13;
533   else if (gst_structure_has_name (structure, "image/png"))
534     flags = 14;
535   gst_caps_unref (caps);
536
537   if (!flags) {
538     GST_WARNING_OBJECT (qtmux, "preview image format not supported");
539     goto done;
540   }
541
542   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT
543       " -> image size %d", GST_FOURCC_ARGS (fourcc), GST_BUFFER_SIZE (buf));
544   atom_moov_add_tag (qtmux->moov, fourcc, flags, GST_BUFFER_DATA (buf),
545       GST_BUFFER_SIZE (buf));
546 done:
547   g_value_unset (&value);
548 }
549
550 static void
551 gst_qt_mux_add_3gp_str (GstQTMux * qtmux, const GstTagList * list,
552     const char *tag, const char *tag2, guint32 fourcc)
553 {
554   gchar *str = NULL;
555   guint number;
556
557   g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_STRING);
558   g_return_if_fail (!tag2 || gst_tag_get_type (tag2) == G_TYPE_UINT);
559
560   if (!gst_tag_list_get_string (list, tag, &str) || !str)
561     return;
562
563   if (tag2)
564     if (!gst_tag_list_get_uint (list, tag2, &number))
565       tag2 = NULL;
566
567   if (!tag2) {
568     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
569         GST_FOURCC_ARGS (fourcc), str);
570     atom_moov_add_3gp_str_tag (qtmux->moov, fourcc, str);
571   } else {
572     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s/%d",
573         GST_FOURCC_ARGS (fourcc), str, number);
574     atom_moov_add_3gp_str_int_tag (qtmux->moov, fourcc, str, number);
575   }
576
577   g_free (str);
578 }
579
580 static void
581 gst_qt_mux_add_3gp_date (GstQTMux * qtmux, const GstTagList * list,
582     const char *tag, const char *tag2, guint32 fourcc)
583 {
584   GDate *date = NULL;
585   GDateYear year;
586
587   g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_DATE);
588
589   if (!gst_tag_list_get_date (list, tag, &date) || !date)
590     return;
591
592   year = g_date_get_year (date);
593
594   if (year == G_DATE_BAD_YEAR) {
595     GST_WARNING_OBJECT (qtmux, "invalid date in tag");
596     return;
597   }
598
599   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %d",
600       GST_FOURCC_ARGS (fourcc), year);
601   atom_moov_add_3gp_uint_tag (qtmux->moov, fourcc, year);
602 }
603
604 static void
605 gst_qt_mux_add_3gp_location (GstQTMux * qtmux, const GstTagList * list,
606     const char *tag, const char *tag2, guint32 fourcc)
607 {
608   gdouble latitude = -360, longitude = -360, altitude = 0;
609   gchar *location = NULL;
610   guint8 *data, *ddata;
611   gint size = 0, len = 0;
612   gboolean ret = FALSE;
613
614   g_return_if_fail (strcmp (tag, GST_TAG_GEO_LOCATION_NAME) == 0);
615
616   ret = gst_tag_list_get_string (list, tag, &location);
617   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LONGITUDE,
618       &longitude);
619   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LATITUDE,
620       &latitude);
621   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_ELEVATION,
622       &altitude);
623
624   if (!ret)
625     return;
626
627   if (location)
628     len = strlen (location);
629   size += len + 1 + 2;
630
631   /* role + (long, lat, alt) + body + notes */
632   size += 1 + 3 * 4 + 1 + 1;
633
634   data = ddata = g_malloc (size);
635
636   /* language tag */
637   GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
638   /* location */
639   if (location)
640     memcpy (data + 2, location, len);
641   GST_WRITE_UINT8 (data + 2 + len, 0);
642   data += len + 1 + 2;
643   /* role */
644   GST_WRITE_UINT8 (data, 0);
645   /* long, lat, alt */
646   GST_WRITE_UINT32_BE (data + 1, (guint32) (longitude * 65536.0));
647   GST_WRITE_UINT32_BE (data + 5, (guint32) (latitude * 65536.0));
648   GST_WRITE_UINT32_BE (data + 9, (guint32) (altitude * 65536.0));
649   /* neither astronomical body nor notes */
650   GST_WRITE_UINT16_BE (data + 13, 0);
651
652   GST_DEBUG_OBJECT (qtmux, "Adding tag 'loci'");
653   atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
654   g_free (ddata);
655 }
656
657 static void
658 gst_qt_mux_add_3gp_keywords (GstQTMux * qtmux, const GstTagList * list,
659     const char *tag, const char *tag2, guint32 fourcc)
660 {
661   gchar *keywords = NULL;
662   guint8 *data, *ddata;
663   gint size = 0, i;
664   gchar **kwds;
665
666   g_return_if_fail (strcmp (tag, GST_TAG_KEYWORDS) == 0);
667
668   if (!gst_tag_list_get_string (list, tag, &keywords) || !keywords)
669     return;
670
671   kwds = g_strsplit (keywords, ",", 0);
672
673   size = 0;
674   for (i = 0; kwds[i]; i++) {
675     /* size byte + null-terminator */
676     size += strlen (kwds[i]) + 1 + 1;
677   }
678
679   /* language tag + count + keywords */
680   size += 2 + 1;
681
682   data = ddata = g_malloc (size);
683
684   /* language tag */
685   GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
686   /* count */
687   GST_WRITE_UINT8 (data + 2, i);
688   data += 3;
689   /* keywords */
690   for (i = 0; kwds[i]; ++i) {
691     gint len = strlen (kwds[i]);
692
693     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
694         GST_FOURCC_ARGS (fourcc), kwds[i]);
695     /* size */
696     GST_WRITE_UINT8 (data, len + 1);
697     memcpy (data + 1, kwds[i], len + 1);
698     data += len + 2;
699   }
700
701   g_strfreev (kwds);
702
703   atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
704   g_free (ddata);
705 }
706
707 static gboolean
708 gst_qt_mux_parse_classification_string (GstQTMux * qtmux, const gchar * input,
709     guint32 * p_fourcc, guint16 * p_table, gchar ** p_content)
710 {
711   guint32 fourcc;
712   gint table;
713   gint size;
714   const gchar *data;
715
716   data = input;
717   size = strlen (input);
718
719   if (size < 4 + 3 + 1 + 1 + 1) {
720     /* at least the minimum xxxx://y/z */
721     GST_WARNING_OBJECT (qtmux, "Classification tag input (%s) too short, "
722         "ignoring", input);
723     return FALSE;
724   }
725
726   /* read the fourcc */
727   memcpy (&fourcc, data, 4);
728   size -= 4;
729   data += 4;
730
731   if (strncmp (data, "://", 3) != 0) {
732     goto mismatch;
733   }
734   data += 3;
735   size -= 3;
736
737   /* read the table number */
738   if (sscanf (data, "%d", &table) != 1) {
739     goto mismatch;
740   }
741   if (table < 0) {
742     GST_WARNING_OBJECT (qtmux, "Invalid table number in classification tag (%d)"
743         ", table numbers should be positive, ignoring tag", table);
744     return FALSE;
745   }
746
747   /* find the next / */
748   while (size > 0 && data[0] != '/') {
749     data += 1;
750     size -= 1;
751   }
752   if (size == 0) {
753     goto mismatch;
754   }
755   g_assert (data[0] == '/');
756
757   /* skip the '/' */
758   data += 1;
759   size -= 1;
760   if (size == 0) {
761     goto mismatch;
762   }
763
764   /* read up the rest of the string */
765   *p_content = g_strdup (data);
766   *p_table = (guint16) table;
767   *p_fourcc = fourcc;
768   return TRUE;
769
770 mismatch:
771   {
772     GST_WARNING_OBJECT (qtmux, "Ignoring classification tag as "
773         "input (%s) didn't match the expected entitycode://table/content",
774         input);
775     return FALSE;
776   }
777 }
778
779 static void
780 gst_qt_mux_add_3gp_classification (GstQTMux * qtmux, const GstTagList * list,
781     const char *tag, const char *tag2, guint32 fourcc)
782 {
783   gchar *clsf_data = NULL;
784   gint size = 0;
785   guint32 entity = 0;
786   guint16 table = 0;
787   gchar *content = NULL;
788   guint8 *data;
789
790   g_return_if_fail (strcmp (tag, GST_TAG_3GP_CLASSIFICATION) == 0);
791
792   if (!gst_tag_list_get_string (list, tag, &clsf_data) || !clsf_data)
793     return;
794
795   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
796       GST_FOURCC_ARGS (fourcc), clsf_data);
797
798   /* parse the string, format is:
799    * entityfourcc://table/content
800    */
801   gst_qt_mux_parse_classification_string (qtmux, clsf_data, &entity, &table,
802       &content);
803   g_free (clsf_data);
804   /* +1 for the \0 */
805   size = strlen (content) + 1;
806
807   /* now we have everything, build the atom
808    * atom description is at 3GPP TS 26.244 V8.2.0 (2009-09) */
809   data = g_malloc (4 + 2 + 2 + size);
810   GST_WRITE_UINT32_LE (data, entity);
811   GST_WRITE_UINT16_BE (data + 4, (guint16) table);
812   GST_WRITE_UINT16_BE (data + 6, 0);
813   memcpy (data + 8, content, size);
814   g_free (content);
815
816   atom_moov_add_3gp_tag (qtmux->moov, fourcc, data, 4 + 2 + 2 + size);
817   g_free (data);
818 }
819
820 typedef void (*GstQTMuxAddTagFunc) (GstQTMux * mux, const GstTagList * list,
821     const char *tag, const char *tag2, guint32 fourcc);
822
823 /*
824  * Struct to record mappings from gstreamer tags to fourcc codes
825  */
826 typedef struct _GstTagToFourcc
827 {
828   guint32 fourcc;
829   const gchar *gsttag;
830   const gchar *gsttag2;
831   const GstQTMuxAddTagFunc func;
832 } GstTagToFourcc;
833
834 /* tag list tags to fourcc matching */
835 static const GstTagToFourcc tag_matches_mp4[] = {
836   {FOURCC__alb, GST_TAG_ALBUM, NULL, gst_qt_mux_add_mp4_tag},
837   {FOURCC_soal, GST_TAG_ALBUM_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
838   {FOURCC__ART, GST_TAG_ARTIST, NULL, gst_qt_mux_add_mp4_tag},
839   {FOURCC_soar, GST_TAG_ARTIST_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
840   {FOURCC_aART, GST_TAG_ALBUM_ARTIST, NULL, gst_qt_mux_add_mp4_tag},
841   {FOURCC_soaa, GST_TAG_ALBUM_ARTIST_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
842   {FOURCC__cmt, GST_TAG_COMMENT, NULL, gst_qt_mux_add_mp4_tag},
843   {FOURCC__wrt, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_mp4_tag},
844   {FOURCC_soco, GST_TAG_COMPOSER_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
845   {FOURCC_tvsh, GST_TAG_SHOW_NAME, NULL, gst_qt_mux_add_mp4_tag},
846   {FOURCC_sosn, GST_TAG_SHOW_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
847   {FOURCC_tvsn, GST_TAG_SHOW_SEASON_NUMBER, NULL, gst_qt_mux_add_mp4_tag},
848   {FOURCC_tves, GST_TAG_SHOW_EPISODE_NUMBER, NULL, gst_qt_mux_add_mp4_tag},
849   {FOURCC__gen, GST_TAG_GENRE, NULL, gst_qt_mux_add_mp4_tag},
850   {FOURCC__nam, GST_TAG_TITLE, NULL, gst_qt_mux_add_mp4_tag},
851   {FOURCC_sonm, GST_TAG_TITLE_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
852   {FOURCC_perf, GST_TAG_PERFORMER, NULL, gst_qt_mux_add_mp4_tag},
853   {FOURCC__grp, GST_TAG_GROUPING, NULL, gst_qt_mux_add_mp4_tag},
854   {FOURCC__des, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_mp4_tag},
855   {FOURCC__lyr, GST_TAG_LYRICS, NULL, gst_qt_mux_add_mp4_tag},
856   {FOURCC__too, GST_TAG_ENCODER, NULL, gst_qt_mux_add_mp4_tag},
857   {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_mp4_tag},
858   {FOURCC_keyw, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_mp4_tag},
859   {FOURCC__day, GST_TAG_DATE, NULL, gst_qt_mux_add_mp4_date},
860   {FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE, NULL, gst_qt_mux_add_mp4_tag},
861   {FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT,
862       gst_qt_mux_add_mp4_tag},
863   {FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT,
864       gst_qt_mux_add_mp4_tag},
865   {FOURCC_covr, GST_TAG_PREVIEW_IMAGE, NULL, gst_qt_mux_add_mp4_cover},
866   {0, NULL,}
867 };
868
869 static const GstTagToFourcc tag_matches_3gp[] = {
870   {FOURCC_titl, GST_TAG_TITLE, NULL, gst_qt_mux_add_3gp_str},
871   {FOURCC_dscp, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_3gp_str},
872   {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_3gp_str},
873   {FOURCC_perf, GST_TAG_ARTIST, NULL, gst_qt_mux_add_3gp_str},
874   {FOURCC_auth, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_3gp_str},
875   {FOURCC_gnre, GST_TAG_GENRE, NULL, gst_qt_mux_add_3gp_str},
876   {FOURCC_kywd, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_3gp_keywords},
877   {FOURCC_yrrc, GST_TAG_DATE, NULL, gst_qt_mux_add_3gp_date},
878   {FOURCC_albm, GST_TAG_ALBUM, GST_TAG_TRACK_NUMBER, gst_qt_mux_add_3gp_str},
879   {FOURCC_loci, GST_TAG_GEO_LOCATION_NAME, NULL, gst_qt_mux_add_3gp_location},
880   {FOURCC_clsf, GST_TAG_3GP_CLASSIFICATION, NULL,
881       gst_qt_mux_add_3gp_classification},
882   {0, NULL,}
883 };
884
885 /* qtdemux produces these for atoms it cannot parse */
886 #define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag"
887
888 static void
889 gst_qt_mux_add_xmp_tags (GstQTMux * qtmux, const GstTagList * list)
890 {
891   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
892
893   /* adobe specs only have 'quicktime' and 'mp4',
894    * but I guess we can extrapolate to gpp.
895    * Keep mj2 out for now as we don't add any tags for it yet.
896    * If you have further info about xmp on these formats, please share */
897   if (qtmux_klass->format == GST_QT_MUX_FORMAT_MJ2)
898     return;
899
900   GST_DEBUG_OBJECT (qtmux, "Adding xmp tags");
901
902   if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT) {
903     atom_moov_add_xmp_tags (qtmux->moov, list);
904   } else {
905     /* for isom/mp4, it is a top level uuid atom */
906     AtomInfo *ainfo = build_uuid_xmp_atom (list);
907     if (ainfo) {
908       qtmux->extra_atoms = g_slist_prepend (qtmux->extra_atoms, ainfo);
909     }
910   }
911 }
912
913 static void
914 gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list)
915 {
916   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
917   guint32 fourcc;
918   gint i;
919   const gchar *tag, *tag2;
920   const GstTagToFourcc *tag_matches;
921
922   switch (qtmux_klass->format) {
923     case GST_QT_MUX_FORMAT_3GP:
924       tag_matches = tag_matches_3gp;
925       break;
926     case GST_QT_MUX_FORMAT_MJ2:
927       tag_matches = NULL;
928       break;
929     default:
930       /* sort of iTunes style for mp4 and QT (?) */
931       tag_matches = tag_matches_mp4;
932       break;
933   }
934
935   if (!tag_matches)
936     return;
937
938   for (i = 0; tag_matches[i].fourcc; i++) {
939     fourcc = tag_matches[i].fourcc;
940     tag = tag_matches[i].gsttag;
941     tag2 = tag_matches[i].gsttag2;
942
943     g_assert (tag_matches[i].func);
944     tag_matches[i].func (qtmux, list, tag, tag2, fourcc);
945   }
946
947   /* add unparsed blobs if present */
948   if (gst_tag_exists (GST_QT_DEMUX_PRIVATE_TAG)) {
949     guint num_tags;
950
951     num_tags = gst_tag_list_get_tag_size (list, GST_QT_DEMUX_PRIVATE_TAG);
952     for (i = 0; i < num_tags; ++i) {
953       const GValue *val;
954       GstBuffer *buf;
955       GstCaps *caps = NULL;
956
957       val = gst_tag_list_get_value_index (list, GST_QT_DEMUX_PRIVATE_TAG, i);
958       buf = (GstBuffer *) gst_value_get_mini_object (val);
959
960       if (buf && (caps = gst_buffer_get_caps (buf))) {
961         GstStructure *s;
962         const gchar *style = NULL;
963
964         GST_DEBUG_OBJECT (qtmux, "Found private tag %d/%d; size %d, caps %"
965             GST_PTR_FORMAT, i, num_tags, GST_BUFFER_SIZE (buf), caps);
966         s = gst_caps_get_structure (caps, 0);
967         if (s && (style = gst_structure_get_string (s, "style"))) {
968           /* try to prevent some style tag ending up into another variant
969            * (todo: make into a list if more cases) */
970           if ((strcmp (style, "itunes") == 0 &&
971                   qtmux_klass->format == GST_QT_MUX_FORMAT_MP4) ||
972               (strcmp (style, "iso") == 0 &&
973                   qtmux_klass->format == GST_QT_MUX_FORMAT_3GP)) {
974             GST_DEBUG_OBJECT (qtmux, "Adding private tag");
975             atom_moov_add_blob_tag (qtmux->moov, GST_BUFFER_DATA (buf),
976                 GST_BUFFER_SIZE (buf));
977           }
978         }
979         gst_caps_unref (caps);
980       }
981     }
982   }
983
984   return;
985 }
986
987 /*
988  * Gets the tagsetter iface taglist and puts the known tags
989  * into the output stream
990  */
991 static void
992 gst_qt_mux_setup_metadata (GstQTMux * qtmux)
993 {
994   const GstTagList *tags;
995
996   GST_OBJECT_LOCK (qtmux);
997   tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (qtmux));
998   GST_OBJECT_UNLOCK (qtmux);
999
1000   GST_LOG_OBJECT (qtmux, "tags: %" GST_PTR_FORMAT, tags);
1001
1002   if (tags && !gst_tag_list_is_empty (tags)) {
1003     GstTagList *copy = gst_tag_list_copy (tags);
1004
1005     GST_DEBUG_OBJECT (qtmux, "Removing bogus tags");
1006     gst_tag_list_remove_tag (copy, GST_TAG_VIDEO_CODEC);
1007     gst_tag_list_remove_tag (copy, GST_TAG_AUDIO_CODEC);
1008     gst_tag_list_remove_tag (copy, GST_TAG_CONTAINER_FORMAT);
1009
1010     GST_DEBUG_OBJECT (qtmux, "Formatting tags");
1011     gst_qt_mux_add_metadata_tags (qtmux, copy);
1012     gst_qt_mux_add_xmp_tags (qtmux, copy);
1013     gst_tag_list_free (copy);
1014   } else {
1015     GST_DEBUG_OBJECT (qtmux, "No tags received");
1016   }
1017 }
1018
1019 static GstFlowReturn
1020 gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset,
1021     gboolean mind_fast)
1022 {
1023   GstFlowReturn res;
1024   guint8 *data;
1025   guint size;
1026
1027   g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
1028
1029   data = GST_BUFFER_DATA (buf);
1030   size = GST_BUFFER_SIZE (buf);
1031
1032   GST_LOG_OBJECT (qtmux, "sending buffer size %d", size);
1033
1034   if (mind_fast && qtmux->fast_start_file) {
1035     gint ret;
1036
1037     GST_LOG_OBJECT (qtmux, "to temporary file");
1038     ret = fwrite (data, sizeof (guint8), size, qtmux->fast_start_file);
1039     gst_buffer_unref (buf);
1040     if (ret != size)
1041       goto write_error;
1042     else
1043       res = GST_FLOW_OK;
1044   } else {
1045     GST_LOG_OBJECT (qtmux, "downstream");
1046
1047     buf = gst_buffer_make_metadata_writable (buf);
1048     gst_buffer_set_caps (buf, GST_PAD_CAPS (qtmux->srcpad));
1049     res = gst_pad_push (qtmux->srcpad, buf);
1050   }
1051
1052   if (G_LIKELY (offset))
1053     *offset += size;
1054
1055   return res;
1056
1057   /* ERRORS */
1058 write_error:
1059   {
1060     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
1061         ("Failed to write to temporary file"), GST_ERROR_SYSTEM);
1062     return GST_FLOW_ERROR;
1063   }
1064 }
1065
1066 static GstFlowReturn
1067 gst_qt_mux_send_buffered_data (GstQTMux * qtmux, guint64 * offset)
1068 {
1069   GstFlowReturn ret = GST_FLOW_OK;
1070   GstBuffer *buf = NULL;
1071
1072   if (fflush (qtmux->fast_start_file))
1073     goto flush_failed;
1074
1075 #ifdef HAVE_FSEEKO
1076   if (fseeko (qtmux->fast_start_file, (off_t) 0, SEEK_SET) != 0)
1077     goto seek_failed;
1078 #elif defined (G_OS_UNIX) || defined (G_OS_WIN32)
1079   if (lseek (fileno (qtmux->fast_start_file), (off_t) 0,
1080           SEEK_SET) == (off_t) - 1)
1081     goto seek_failed;
1082 #else
1083   if (fseek (qtmux->fast_start_file, (long) 0, SEEK_SET) != 0)
1084     goto seek_failed;
1085 #endif
1086
1087   /* hm, this could all take a really really long time,
1088    * but there may not be another way to get moov atom first
1089    * (somehow optimize copy?) */
1090   GST_DEBUG_OBJECT (qtmux, "Sending buffered data");
1091   while (ret == GST_FLOW_OK) {
1092     gint r;
1093     const int bufsize = 4096;
1094
1095     buf = gst_buffer_new_and_alloc (bufsize);
1096     r = fread (GST_BUFFER_DATA (buf), sizeof (guint8), bufsize,
1097         qtmux->fast_start_file);
1098     if (r == 0)
1099       break;
1100     GST_BUFFER_SIZE (buf) = r;
1101     GST_LOG_OBJECT (qtmux, "Pushing buffered buffer of size %d", r);
1102     ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
1103     buf = NULL;
1104   }
1105   if (buf)
1106     gst_buffer_unref (buf);
1107
1108   return ret;
1109
1110   /* ERRORS */
1111 flush_failed:
1112   {
1113     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
1114         ("Failed to flush temporary file"), GST_ERROR_SYSTEM);
1115     ret = GST_FLOW_ERROR;
1116     return ret;
1117   }
1118 seek_failed:
1119   {
1120     GST_ELEMENT_ERROR (qtmux, RESOURCE, SEEK,
1121         ("Failed to seek temporary file"), GST_ERROR_SYSTEM);
1122     ret = GST_FLOW_ERROR;
1123     return ret;
1124   }
1125 }
1126
1127 /*
1128  * Sends the initial mdat atom fields (size fields and fourcc type),
1129  * the subsequent buffers are considered part of it's data.
1130  * As we can't predict the amount of data that we are going to place in mdat
1131  * we need to record the position of the size field in the stream so we can
1132  * seek back to it later and update when the streams have finished.
1133  */
1134 static GstFlowReturn
1135 gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size,
1136     gboolean extended)
1137 {
1138   Atom *node_header;
1139   GstBuffer *buf;
1140   guint8 *data = NULL;
1141   guint64 offset = 0;
1142
1143   GST_DEBUG_OBJECT (qtmux, "Sending mdat's atom header, "
1144       "size %" G_GUINT64_FORMAT, size);
1145
1146   node_header = g_malloc0 (sizeof (Atom));
1147   node_header->type = FOURCC_mdat;
1148   if (extended) {
1149     /* use extended size */
1150     node_header->size = 1;
1151     node_header->extended_size = 0;
1152     if (size)
1153       node_header->extended_size = size + 16;
1154   } else {
1155     node_header->size = size + 8;
1156   }
1157
1158   size = offset = 0;
1159   if (atom_copy_data (node_header, &data, &size, &offset) == 0)
1160     goto serialize_error;
1161
1162   buf = gst_buffer_new ();
1163   GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
1164   GST_BUFFER_SIZE (buf) = offset;
1165
1166   g_free (node_header);
1167
1168   GST_LOG_OBJECT (qtmux, "Pushing mdat start");
1169   return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
1170
1171   /* ERRORS */
1172 serialize_error:
1173   {
1174     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1175         ("Failed to serialize mdat"));
1176     return GST_FLOW_ERROR;
1177   }
1178 }
1179
1180 /*
1181  * We get the position of the mdat size field, seek back to it
1182  * and overwrite with the real value
1183  */
1184 static GstFlowReturn
1185 gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos,
1186     guint64 mdat_size, guint64 * offset)
1187 {
1188   GstEvent *event;
1189   GstBuffer *buf;
1190   gboolean large_file;
1191
1192   large_file = (mdat_size > MDAT_LARGE_FILE_LIMIT);
1193
1194   if (large_file)
1195     mdat_pos += 8;
1196
1197   /* seek and rewrite the header */
1198   event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES,
1199       mdat_pos, GST_CLOCK_TIME_NONE, 0);
1200   gst_pad_push_event (qtmux->srcpad, event);
1201
1202   if (large_file) {
1203     buf = gst_buffer_new_and_alloc (sizeof (guint64));
1204     GST_WRITE_UINT64_BE (GST_BUFFER_DATA (buf), mdat_size + 16);
1205   } else {
1206     guint8 *data;
1207
1208     buf = gst_buffer_new_and_alloc (16);
1209     data = GST_BUFFER_DATA (buf);
1210     GST_WRITE_UINT32_BE (data, 8);
1211     GST_WRITE_UINT32_LE (data + 4, FOURCC_free);
1212     GST_WRITE_UINT32_BE (data + 8, mdat_size + 8);
1213     GST_WRITE_UINT32_LE (data + 12, FOURCC_mdat);
1214   }
1215
1216   return gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
1217 }
1218
1219 static GstFlowReturn
1220 gst_qt_mux_send_ftyp (GstQTMux * qtmux, guint64 * off)
1221 {
1222   GstBuffer *buf;
1223   guint64 size = 0, offset = 0;
1224   guint8 *data = NULL;
1225
1226   GST_DEBUG_OBJECT (qtmux, "Sending ftyp atom");
1227
1228   if (!atom_ftyp_copy_data (qtmux->ftyp, &data, &size, &offset))
1229     goto serialize_error;
1230
1231   buf = gst_buffer_new ();
1232   GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
1233   GST_BUFFER_SIZE (buf) = offset;
1234
1235   GST_LOG_OBJECT (qtmux, "Pushing ftyp");
1236   return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
1237
1238   /* ERRORS */
1239 serialize_error:
1240   {
1241     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1242         ("Failed to serialize ftyp"));
1243     return GST_FLOW_ERROR;
1244   }
1245 }
1246
1247 static void
1248 gst_qt_mux_prepare_ftyp (GstQTMux * qtmux, AtomFTYP ** p_ftyp,
1249     GstBuffer ** p_prefix)
1250 {
1251   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1252   guint32 major, version;
1253   GList *comp;
1254   GstBuffer *prefix = NULL;
1255   AtomFTYP *ftyp = NULL;
1256
1257   GST_DEBUG_OBJECT (qtmux, "Preparing ftyp and possible prefix atom");
1258
1259   /* init and send context and ftyp based on current property state */
1260   gst_qt_mux_map_format_to_header (qtmux_klass->format, &prefix, &major,
1261       &version, &comp, qtmux->moov, qtmux->longest_chunk,
1262       qtmux->fast_start_file != NULL);
1263   ftyp = atom_ftyp_new (qtmux->context, major, version, comp);
1264   if (comp)
1265     g_list_free (comp);
1266   if (prefix) {
1267     if (p_prefix)
1268       *p_prefix = prefix;
1269     else
1270       gst_buffer_unref (prefix);
1271   }
1272   *p_ftyp = ftyp;
1273 }
1274
1275 static GstFlowReturn
1276 gst_qt_mux_prepare_and_send_ftyp (GstQTMux * qtmux)
1277 {
1278   GstFlowReturn ret = GST_FLOW_OK;
1279   GstBuffer *prefix = NULL;
1280
1281   GST_DEBUG_OBJECT (qtmux, "Preparing to send ftyp atom");
1282
1283   /* init and send context and ftyp based on current property state */
1284   if (qtmux->ftyp) {
1285     atom_ftyp_free (qtmux->ftyp);
1286     qtmux->ftyp = NULL;
1287   }
1288   gst_qt_mux_prepare_ftyp (qtmux, &qtmux->ftyp, &prefix);
1289   if (prefix) {
1290     ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE);
1291     if (ret != GST_FLOW_OK)
1292       return ret;
1293   }
1294   return gst_qt_mux_send_ftyp (qtmux, &qtmux->header_size);
1295 }
1296
1297 static GstFlowReturn
1298 gst_qt_mux_start_file (GstQTMux * qtmux)
1299 {
1300   GstFlowReturn ret = GST_FLOW_OK;
1301
1302   GST_DEBUG_OBJECT (qtmux, "starting file");
1303
1304   /* let downstream know we think in BYTES and expect to do seeking later on */
1305   gst_pad_push_event (qtmux->srcpad,
1306       gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0));
1307
1308   /* initialize our moov recovery file */
1309   GST_OBJECT_LOCK (qtmux);
1310   if (qtmux->moov_recov_file_path) {
1311     GST_DEBUG_OBJECT (qtmux, "Openning moov recovery file: %s",
1312         qtmux->moov_recov_file_path);
1313     qtmux->moov_recov_file = g_fopen (qtmux->moov_recov_file_path, "wb+");
1314     if (qtmux->moov_recov_file == NULL) {
1315       GST_WARNING_OBJECT (qtmux, "Failed to open moov recovery file in %s",
1316           qtmux->moov_recov_file_path);
1317     } else {
1318       GSList *walk;
1319       gboolean fail = FALSE;
1320       AtomFTYP *ftyp = NULL;
1321       GstBuffer *prefix = NULL;
1322
1323       gst_qt_mux_prepare_ftyp (qtmux, &ftyp, &prefix);
1324
1325       if (!atoms_recov_write_headers (qtmux->moov_recov_file, ftyp, prefix,
1326               qtmux->moov, qtmux->timescale,
1327               g_slist_length (qtmux->sinkpads))) {
1328         GST_WARNING_OBJECT (qtmux, "Failed to write moov recovery file "
1329             "headers");
1330         fail = TRUE;
1331       }
1332
1333       atom_ftyp_free (ftyp);
1334       if (prefix)
1335         gst_buffer_unref (prefix);
1336
1337       for (walk = qtmux->sinkpads; walk && !fail; walk = g_slist_next (walk)) {
1338         GstCollectData *cdata = (GstCollectData *) walk->data;
1339         GstQTPad *qpad = (GstQTPad *) cdata;
1340         /* write info for each stream */
1341         fail = atoms_recov_write_trak_info (qtmux->moov_recov_file, qpad->trak);
1342         if (fail) {
1343           GST_WARNING_OBJECT (qtmux, "Failed to write trak info to recovery "
1344               "file");
1345         }
1346       }
1347       if (fail) {
1348         /* cleanup */
1349         fclose (qtmux->moov_recov_file);
1350         qtmux->moov_recov_file = NULL;
1351         GST_WARNING_OBJECT (qtmux, "An error was detected while writing to "
1352             "recover file, moov recovery won't work");
1353       }
1354     }
1355   }
1356   GST_OBJECT_UNLOCK (qtmux);
1357
1358   /* 
1359    * send mdat header if already needed, and mark position for later update.
1360    * We don't send ftyp now if we are on fast start mode, because we can
1361    * better fine tune using the information we gather to create the whole moov
1362    * atom.
1363    */
1364   if (qtmux->fast_start) {
1365     GST_OBJECT_LOCK (qtmux);
1366     qtmux->fast_start_file = g_fopen (qtmux->fast_start_file_path, "wb+");
1367     if (!qtmux->fast_start_file)
1368       goto open_failed;
1369     GST_OBJECT_UNLOCK (qtmux);
1370
1371     /* send a dummy buffer for preroll */
1372     ret = gst_qt_mux_send_buffer (qtmux, gst_buffer_new (), NULL, FALSE);
1373     if (ret != GST_FLOW_OK)
1374       goto exit;
1375
1376   } else {
1377     ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
1378     if (ret != GST_FLOW_OK) {
1379       goto exit;
1380     }
1381
1382     /* extended to ensure some spare space */
1383     qtmux->mdat_pos = qtmux->header_size;
1384     ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE);
1385   }
1386
1387 exit:
1388   return ret;
1389
1390   /* ERRORS */
1391 open_failed:
1392   {
1393     GST_ELEMENT_ERROR (qtmux, RESOURCE, OPEN_READ_WRITE,
1394         (("Could not open temporary file \"%s\""), qtmux->fast_start_file_path),
1395         GST_ERROR_SYSTEM);
1396     GST_OBJECT_UNLOCK (qtmux);
1397     return GST_FLOW_ERROR;
1398   }
1399 }
1400
1401 static GstFlowReturn
1402 gst_qt_mux_stop_file (GstQTMux * qtmux)
1403 {
1404   gboolean ret = GST_FLOW_OK;
1405   GstBuffer *buffer = NULL;
1406   guint64 offset = 0, size = 0;
1407   guint8 *data;
1408   GSList *walk;
1409   gboolean large_file;
1410   guint32 timescale;
1411   GstClockTime first_ts = GST_CLOCK_TIME_NONE;
1412
1413   GST_DEBUG_OBJECT (qtmux, "Updating remaining values and sending last data");
1414
1415   /* pushing last buffers for each pad */
1416   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
1417     GstCollectData *cdata = (GstCollectData *) walk->data;
1418     GstQTPad *qtpad = (GstQTPad *) cdata;
1419
1420     /* send last buffer */
1421     GST_DEBUG_OBJECT (qtmux, "Sending the last buffer for pad %s",
1422         GST_PAD_NAME (qtpad->collect.pad));
1423     ret = gst_qt_mux_add_buffer (qtmux, qtpad, NULL);
1424     if (ret != GST_FLOW_OK)
1425       GST_WARNING_OBJECT (qtmux, "Failed to send last buffer for %s, "
1426           "flow return: %s", GST_PAD_NAME (qtpad->collect.pad),
1427           gst_flow_get_name (ret));
1428   }
1429
1430   GST_OBJECT_LOCK (qtmux);
1431   timescale = qtmux->timescale;
1432   large_file = qtmux->large_file;
1433   GST_OBJECT_UNLOCK (qtmux);
1434
1435   /* inform lower layers of our property wishes, and determine duration.
1436    * Let moov take care of this using its list of traks;
1437    * so that released pads are also included */
1438   GST_DEBUG_OBJECT (qtmux, "Large file support: %d", large_file);
1439   GST_DEBUG_OBJECT (qtmux, "Updating timescale to %" G_GUINT32_FORMAT,
1440       timescale);
1441   atom_moov_update_timescale (qtmux->moov, timescale);
1442   atom_moov_set_64bits (qtmux->moov, large_file);
1443   atom_moov_update_duration (qtmux->moov);
1444
1445   /* check for late streams */
1446   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
1447     GstCollectData *cdata = (GstCollectData *) walk->data;
1448     GstQTPad *qtpad = (GstQTPad *) cdata;
1449
1450     if (!GST_CLOCK_TIME_IS_VALID (first_ts) ||
1451         (GST_CLOCK_TIME_IS_VALID (qtpad->first_ts) &&
1452             qtpad->first_ts < first_ts)) {
1453       first_ts = qtpad->first_ts;
1454     }
1455   }
1456   GST_DEBUG_OBJECT (qtmux, "Media first ts selected: %" GST_TIME_FORMAT,
1457       GST_TIME_ARGS (first_ts));
1458   /* add EDTSs for late streams */
1459   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
1460     GstCollectData *cdata = (GstCollectData *) walk->data;
1461     GstQTPad *qtpad = (GstQTPad *) cdata;
1462     guint32 lateness;
1463     guint32 duration;
1464
1465     if (GST_CLOCK_TIME_IS_VALID (qtpad->first_ts) &&
1466         qtpad->first_ts > first_ts + MAX_TOLERATED_LATENESS) {
1467       GST_DEBUG_OBJECT (qtmux, "Pad %s is a late stream by %" GST_TIME_FORMAT,
1468           GST_PAD_NAME (qtpad->collect.pad),
1469           GST_TIME_ARGS (qtpad->first_ts - first_ts));
1470       lateness = gst_util_uint64_scale_round (qtpad->first_ts - first_ts,
1471           timescale, GST_SECOND);
1472       duration = qtpad->trak->tkhd.duration;
1473       atom_trak_add_elst_entry (qtpad->trak, lateness, (guint32) - 1,
1474           (guint32) (1 * 65536.0));
1475       atom_trak_add_elst_entry (qtpad->trak, duration, 0,
1476           (guint32) (1 * 65536.0));
1477
1478       /* need to add the empty time to the trak duration */
1479       qtpad->trak->tkhd.duration += lateness;
1480     }
1481   }
1482
1483   /* tags into file metadata */
1484   gst_qt_mux_setup_metadata (qtmux);
1485
1486   large_file = (qtmux->mdat_size > MDAT_LARGE_FILE_LIMIT);
1487   /* if faststart, update the offset of the atoms in the movie with the offset
1488    * that the movie headers before mdat will cause.
1489    * Also, send the ftyp */
1490   if (qtmux->fast_start_file) {
1491     GstFlowReturn flow_ret;
1492     offset = size = 0;
1493
1494     flow_ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
1495     if (flow_ret != GST_FLOW_OK) {
1496       goto ftyp_error;
1497     }
1498     /* copy into NULL to obtain size */
1499     if (!atom_moov_copy_data (qtmux->moov, NULL, &size, &offset))
1500       goto serialize_error;
1501     GST_DEBUG_OBJECT (qtmux, "calculated moov atom size %" G_GUINT64_FORMAT,
1502         offset);
1503     offset += qtmux->header_size + (large_file ? 16 : 8);
1504
1505     /* sum up with the extra atoms size */
1506     for (walk = qtmux->extra_atoms; walk; walk = g_slist_next (walk)) {
1507       guint64 extra_size = 0, extra_offset = 0;
1508       AtomInfo *ainfo = (AtomInfo *) walk->data;
1509
1510       if (!ainfo->copy_data_func (ainfo->atom, NULL, &extra_size,
1511               &extra_offset))
1512         goto serialize_error;
1513       offset += extra_offset;
1514     }
1515   } else
1516     offset = qtmux->header_size;
1517   atom_moov_chunks_add_offset (qtmux->moov, offset);
1518
1519   /* serialize moov */
1520   offset = size = 0;
1521   data = NULL;
1522   GST_LOG_OBJECT (qtmux, "Copying movie header into buffer");
1523   if (!atom_moov_copy_data (qtmux->moov, &data, &size, &offset))
1524     goto serialize_error;
1525
1526   buffer = gst_buffer_new ();
1527   GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer) = data;
1528   GST_BUFFER_SIZE (buffer) = offset;
1529   /* note: as of this point, we no longer care about tracking written data size,
1530    * since there is no more use for it anyway */
1531   GST_DEBUG_OBJECT (qtmux, "Pushing movie atoms");
1532   gst_qt_mux_send_buffer (qtmux, buffer, NULL, FALSE);
1533
1534   /* push extra top-level atoms */
1535   for (walk = qtmux->extra_atoms; walk; walk = g_slist_next (walk)) {
1536     AtomInfo *ainfo = (AtomInfo *) walk->data;
1537
1538     offset = size = 0;
1539     data = NULL;
1540     if (!ainfo->copy_data_func (ainfo->atom, &data, &size, &offset))
1541       goto serialize_error;
1542
1543     buffer = gst_buffer_new ();
1544     GST_BUFFER_MALLOCDATA (buffer) = GST_BUFFER_DATA (buffer) = data;
1545     GST_BUFFER_SIZE (buffer) = offset;
1546     GST_DEBUG_OBJECT (qtmux, "Pushing extra top-level atom %" GST_FOURCC_FORMAT,
1547         GST_FOURCC_ARGS (ainfo->atom->type));
1548     gst_qt_mux_send_buffer (qtmux, buffer, NULL, FALSE);
1549   }
1550
1551   /* if needed, send mdat atom and move buffered data into it */
1552   if (qtmux->fast_start_file) {
1553     /* mdat size = accumulated (buffered data) + mdat atom header */
1554     ret = gst_qt_mux_send_mdat_header (qtmux, NULL, qtmux->mdat_size,
1555         large_file);
1556     if (ret != GST_FLOW_OK)
1557       return ret;
1558     ret = gst_qt_mux_send_buffered_data (qtmux, NULL);
1559     if (ret != GST_FLOW_OK)
1560       return ret;
1561   } else {
1562     /* mdat needs update iff not using faststart */
1563     GST_DEBUG_OBJECT (qtmux, "updating mdat size");
1564     ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
1565         qtmux->mdat_size, NULL);
1566     /* note; no seeking back to the end of file is done,
1567      * since we no longer write anything anyway */
1568   }
1569
1570   return ret;
1571
1572   /* ERRORS */
1573 serialize_error:
1574   {
1575     gst_buffer_unref (buffer);
1576     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1577         ("Failed to serialize moov"));
1578     return GST_FLOW_ERROR;
1579   }
1580 ftyp_error:
1581   {
1582     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), ("Failed to send ftyp"));
1583     return GST_FLOW_ERROR;
1584   }
1585 }
1586
1587 /* check whether @a differs from @b by order of @magn */
1588 static gboolean inline
1589 gst_qtmux_check_difference (GstQTMux * qtmux, GstClockTime a,
1590     GstClockTime b, GstClockTime magn)
1591 {
1592   return ((a - b >= (magn >> 1)) || (b - a >= (magn >> 1)));
1593 }
1594
1595 /*
1596  * Here we push the buffer and update the tables in the track atoms
1597  */
1598 static GstFlowReturn
1599 gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
1600 {
1601   GstBuffer *last_buf = NULL;
1602   GstClockTime duration;
1603   guint nsamples, sample_size;
1604   guint64 scaled_duration, chunk_offset;
1605   gint64 last_dts;
1606   gint64 pts_offset = 0;
1607   gboolean sync = FALSE, do_pts = FALSE;
1608
1609   if (!pad->fourcc)
1610     goto not_negotiated;
1611
1612   /* if this pad has a prepare function, call it */
1613   if (pad->prepare_buf_func != NULL) {
1614     buf = pad->prepare_buf_func (pad, buf, qtmux);
1615   }
1616
1617   last_buf = pad->last_buf;
1618   if (last_buf == NULL) {
1619 #ifndef GST_DISABLE_GST_DEBUG
1620     if (buf == NULL) {
1621       GST_DEBUG_OBJECT (qtmux, "Pad %s has no previous buffer stored and "
1622           "received NULL buffer, doing nothing",
1623           GST_PAD_NAME (pad->collect.pad));
1624     } else {
1625       GST_LOG_OBJECT (qtmux,
1626           "Pad %s has no previous buffer stored, storing now",
1627           GST_PAD_NAME (pad->collect.pad));
1628     }
1629 #endif
1630     pad->last_buf = buf;
1631     return GST_FLOW_OK;
1632   } else
1633     gst_buffer_ref (last_buf);
1634
1635   /* nasty heuristic mess to guestimate dealing with DTS/PTS,
1636    * while also trying to stay close to input ts to preserve sync, so:
1637    * - prefer using input ts where possible
1638    * - if those detected out-of-order (*), and input duration available,
1639    *   mark as out-of-order and fallback to duration
1640    * - if in out-of-order, need to preserve sync between streams, and adding
1641    *   durations might drift, so try to resync when we expect
1642    *   input ts == (sum of durations), which is at some keyframe input frame.
1643    *
1644    * (*) if input ts out-of-order, or if ts differs from (sum of durations)
1645    *     by an (approx) order-of-duration magnitude
1646    */
1647   if (G_LIKELY (buf) && !pad->is_out_of_order) {
1648     if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (last_buf) &&
1649             GST_BUFFER_TIMESTAMP_IS_VALID (buf))) {
1650       if ((GST_BUFFER_TIMESTAMP (buf) < GST_BUFFER_TIMESTAMP (last_buf)) ||
1651           (!GST_CLOCK_TIME_IS_VALID (pad->first_ts) &&
1652               GST_BUFFER_DURATION_IS_VALID (last_buf) &&
1653               gst_qtmux_check_difference (qtmux,
1654                   GST_BUFFER_TIMESTAMP (last_buf) +
1655                   GST_BUFFER_DURATION (last_buf), GST_BUFFER_TIMESTAMP (buf),
1656                   GST_BUFFER_DURATION (last_buf)))) {
1657         GST_DEBUG_OBJECT (qtmux, "detected out-of-order input");
1658         pad->is_out_of_order = TRUE;
1659       }
1660     } else {
1661       /* this is pretty bad */
1662       GST_WARNING_OBJECT (qtmux, "missing input timestamp");
1663       /* fall back to durations */
1664       pad->is_out_of_order = TRUE;
1665     }
1666   }
1667
1668   /* fall back to duration if last buffer or
1669    * out-of-order (determined previously), otherwise use input ts */
1670   if (buf == NULL || pad->is_out_of_order) {
1671     if (!GST_BUFFER_DURATION_IS_VALID (last_buf)) {
1672       /* be forgiving for some possibly last upstream flushed buffer */
1673       if (buf)
1674         goto no_time;
1675       GST_WARNING_OBJECT (qtmux, "no duration for last buffer");
1676       /* iso spec recommends some small value, try 0 */
1677       duration = 0;
1678     } else {
1679       duration = GST_BUFFER_DURATION (last_buf);
1680       /* avoid drift in sum timestamps,
1681        * so use input timestamp for suitable keyframe */
1682       if (buf && !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) &&
1683           GST_BUFFER_TIMESTAMP (buf) >= pad->last_dts &&
1684           !gst_qtmux_check_difference (qtmux, pad->last_dts + duration,
1685               GST_BUFFER_TIMESTAMP (buf), duration)) {
1686         GST_DEBUG_OBJECT (qtmux, "resyncing out-of-order input to ts; "
1687             "replacing %" GST_TIME_FORMAT " by %" GST_TIME_FORMAT,
1688             GST_TIME_ARGS (pad->last_dts + duration),
1689             GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
1690         duration = GST_BUFFER_TIMESTAMP (buf) - pad->last_dts;
1691       }
1692     }
1693   } else {
1694     duration = GST_BUFFER_TIMESTAMP (buf) - GST_BUFFER_TIMESTAMP (last_buf);
1695   }
1696
1697   gst_buffer_replace (&pad->last_buf, buf);
1698
1699   last_dts = gst_util_uint64_scale_round (pad->last_dts,
1700       atom_trak_get_timescale (pad->trak), GST_SECOND);
1701
1702   if (pad->sample_size) {
1703     /* Constant size packets: usually raw audio (with many samples per
1704        buffer (= chunk)), but can also be fixed-packet-size codecs like ADPCM
1705      */
1706     sample_size = pad->sample_size;
1707     if (GST_BUFFER_SIZE (last_buf) % sample_size != 0)
1708       goto fragmented_sample;
1709     /* note: qt raw audio storage warps it implicitly into a timewise
1710      * perfect stream, discarding buffer times */
1711     if (GST_BUFFER_DURATION (last_buf) != GST_CLOCK_TIME_NONE) {
1712       nsamples = gst_util_uint64_scale_round (GST_BUFFER_DURATION (last_buf),
1713           atom_trak_get_timescale (pad->trak), GST_SECOND);
1714     } else {
1715       nsamples = GST_BUFFER_SIZE (last_buf) / sample_size;
1716     }
1717     duration = GST_BUFFER_DURATION (last_buf) / nsamples;
1718
1719     /* timescale = samplerate */
1720     scaled_duration = 1;
1721     pad->last_dts += duration * nsamples;
1722   } else {
1723     nsamples = 1;
1724     sample_size = GST_BUFFER_SIZE (last_buf);
1725     if (pad->have_dts) {
1726       gint64 scaled_dts;
1727       pad->last_dts = GST_BUFFER_OFFSET_END (last_buf);
1728       if ((gint64) (pad->last_dts) < 0) {
1729         scaled_dts = -gst_util_uint64_scale_round (-pad->last_dts,
1730             atom_trak_get_timescale (pad->trak), GST_SECOND);
1731       } else {
1732         scaled_dts = gst_util_uint64_scale_round (pad->last_dts,
1733             atom_trak_get_timescale (pad->trak), GST_SECOND);
1734       }
1735       scaled_duration = scaled_dts - last_dts;
1736       last_dts = scaled_dts;
1737     } else {
1738       /* first convert intended timestamp (in GstClockTime resolution) to
1739        * trak timescale, then derive delta;
1740        * this ensures sums of (scale)delta add up to converted timestamp,
1741        * which only deviates at most 1/scale from timestamp itself */
1742       scaled_duration = gst_util_uint64_scale_round (pad->last_dts + duration,
1743           atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts;
1744       pad->last_dts += duration;
1745     }
1746   }
1747   chunk_offset = qtmux->mdat_size;
1748
1749   GST_LOG_OBJECT (qtmux,
1750       "Pad (%s) dts updated to %" GST_TIME_FORMAT,
1751       GST_PAD_NAME (pad->collect.pad), GST_TIME_ARGS (pad->last_dts));
1752   GST_LOG_OBJECT (qtmux,
1753       "Adding %d samples to track, duration: %" G_GUINT64_FORMAT
1754       " size: %" G_GUINT32_FORMAT " chunk offset: %" G_GUINT64_FORMAT,
1755       nsamples, scaled_duration, sample_size, chunk_offset);
1756
1757   /* might be a sync sample */
1758   if (pad->sync &&
1759       !GST_BUFFER_FLAG_IS_SET (last_buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
1760     GST_LOG_OBJECT (qtmux, "Adding new sync sample entry for track of pad %s",
1761         GST_PAD_NAME (pad->collect.pad));
1762     sync = TRUE;
1763   }
1764
1765   /* optionally calculate ctts entry values
1766    * (if composition-time expected different from decoding-time) */
1767   /* really not recommended:
1768    * - decoder typically takes care of dts/pts issues
1769    * - in case of out-of-order, dts may only be determined as above
1770    *   (e.g. sum of duration), which may be totally different from
1771    *   buffer timestamps in case of multiple segment, non-perfect streams
1772    *  (and just perhaps maybe with some luck segment_to_running_time
1773    *   or segment_to_media_time might get near to it) */
1774   if ((pad->have_dts || qtmux->guess_pts) && pad->is_out_of_order) {
1775     guint64 pts;
1776
1777     pts = gst_util_uint64_scale_round (GST_BUFFER_TIMESTAMP (last_buf),
1778         atom_trak_get_timescale (pad->trak), GST_SECOND);
1779     pts_offset = (gint64) (pts - last_dts);
1780     do_pts = TRUE;
1781     GST_LOG_OBJECT (qtmux, "Adding ctts entry for pad %s: %" G_GINT64_FORMAT,
1782         GST_PAD_NAME (pad->collect.pad), pts_offset);
1783   }
1784
1785   /*
1786    * Each buffer starts a new chunk, so we can assume the buffer
1787    * duration is the chunk duration
1788    */
1789   if (GST_CLOCK_TIME_IS_VALID (duration) && (duration > qtmux->longest_chunk ||
1790           !GST_CLOCK_TIME_IS_VALID (qtmux->longest_chunk))) {
1791     GST_DEBUG_OBJECT (qtmux, "New longest chunk found: %" GST_TIME_FORMAT
1792         ", pad %s", GST_TIME_ARGS (duration), GST_PAD_NAME (pad->collect.pad));
1793     qtmux->longest_chunk = duration;
1794   }
1795
1796   /* if this is the first buffer, store the timestamp */
1797   if (G_UNLIKELY (pad->first_ts == GST_CLOCK_TIME_NONE) && last_buf) {
1798     if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (last_buf))) {
1799       pad->first_ts = GST_BUFFER_TIMESTAMP (last_buf);
1800     } else {
1801       GST_DEBUG_OBJECT (qtmux, "First buffer for pad %s has no timestamp, "
1802           "using 0 as first timestamp", GST_PAD_NAME (pad->collect.pad));
1803       pad->first_ts = 0;
1804     }
1805     GST_DEBUG_OBJECT (qtmux, "Stored first timestamp for pad %s %"
1806         GST_TIME_FORMAT, GST_PAD_NAME (pad->collect.pad),
1807         GST_TIME_ARGS (pad->first_ts));
1808   }
1809
1810   /* now we go and register this buffer/sample all over */
1811   /* note that a new chunk is started each time (not fancy but works) */
1812   if (qtmux->moov_recov_file) {
1813     if (!atoms_recov_write_trak_samples (qtmux->moov_recov_file, pad->trak,
1814             nsamples, scaled_duration, sample_size, chunk_offset, sync, do_pts,
1815             pts_offset)) {
1816       GST_WARNING_OBJECT (qtmux, "Failed to write sample information to "
1817           "recovery file, disabling recovery");
1818       fclose (qtmux->moov_recov_file);
1819       qtmux->moov_recov_file = NULL;
1820     }
1821   }
1822   atom_trak_add_samples (pad->trak, nsamples, scaled_duration, sample_size,
1823       chunk_offset, sync, do_pts, pts_offset);
1824
1825   if (buf)
1826     gst_buffer_unref (buf);
1827
1828   return gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE);
1829
1830   /* ERRORS */
1831 bail:
1832   {
1833     if (buf)
1834       gst_buffer_unref (buf);
1835     gst_buffer_unref (last_buf);
1836     return GST_FLOW_ERROR;
1837   }
1838 no_time:
1839   {
1840     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1841         ("Received buffer without timestamp/duration."));
1842     goto bail;
1843   }
1844 fragmented_sample:
1845   {
1846     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1847         ("Audio buffer contains fragmented sample."));
1848     goto bail;
1849   }
1850 not_negotiated:
1851   {
1852     GST_ELEMENT_ERROR (qtmux, CORE, NEGOTIATION, (NULL),
1853         ("format wasn't negotiated before buffer flow on pad %s",
1854             GST_PAD_NAME (pad->collect.pad)));
1855     if (buf)
1856       gst_buffer_unref (buf);
1857     return GST_FLOW_NOT_NEGOTIATED;
1858   }
1859 }
1860
1861 static GstFlowReturn
1862 gst_qt_mux_collected (GstCollectPads * pads, gpointer user_data)
1863 {
1864   GstFlowReturn ret = GST_FLOW_OK;
1865   GstQTMux *qtmux = GST_QT_MUX_CAST (user_data);
1866   GSList *walk;
1867   GstQTPad *best_pad = NULL;
1868   GstClockTime time, best_time = GST_CLOCK_TIME_NONE;
1869   GstBuffer *buf;
1870
1871   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_STARTED)) {
1872     if ((ret = gst_qt_mux_start_file (qtmux)) != GST_FLOW_OK)
1873       return ret;
1874     else
1875       qtmux->state = GST_QT_MUX_STATE_DATA;
1876   }
1877
1878   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS))
1879     return GST_FLOW_UNEXPECTED;
1880
1881   /* select the best buffer */
1882   walk = qtmux->collect->data;
1883   while (walk) {
1884     GstQTPad *pad;
1885     GstCollectData *data;
1886
1887     data = (GstCollectData *) walk->data;
1888     pad = (GstQTPad *) data;
1889
1890     walk = g_slist_next (walk);
1891
1892     buf = gst_collect_pads_peek (pads, data);
1893     if (buf == NULL) {
1894       GST_LOG_OBJECT (qtmux, "Pad %s has no buffers",
1895           GST_PAD_NAME (pad->collect.pad));
1896       continue;
1897     }
1898     time = GST_BUFFER_TIMESTAMP (buf);
1899     gst_buffer_unref (buf);
1900
1901     if (best_pad == NULL || !GST_CLOCK_TIME_IS_VALID (time) ||
1902         (GST_CLOCK_TIME_IS_VALID (best_time) && time < best_time)) {
1903       best_pad = pad;
1904       best_time = time;
1905     }
1906   }
1907
1908   if (best_pad != NULL) {
1909     GST_LOG_OBJECT (qtmux, "selected pad %s with time %" GST_TIME_FORMAT,
1910         GST_PAD_NAME (best_pad->collect.pad), GST_TIME_ARGS (best_time));
1911     buf = gst_collect_pads_pop (pads, &best_pad->collect);
1912     ret = gst_qt_mux_add_buffer (qtmux, best_pad, buf);
1913   } else {
1914     ret = gst_qt_mux_stop_file (qtmux);
1915     if (ret == GST_FLOW_OK) {
1916       GST_DEBUG_OBJECT (qtmux, "Pushing eos");
1917       gst_pad_push_event (qtmux->srcpad, gst_event_new_eos ());
1918       ret = GST_FLOW_UNEXPECTED;
1919     } else {
1920       GST_WARNING_OBJECT (qtmux, "Failed to stop file: %s",
1921           gst_flow_get_name (ret));
1922     }
1923     qtmux->state = GST_QT_MUX_STATE_EOS;
1924   }
1925
1926   return ret;
1927 }
1928
1929 static gboolean
1930 check_field (GQuark field_id, const GValue * value, gpointer user_data)
1931 {
1932   GstStructure *structure = (GstStructure *) user_data;
1933   const GValue *other = gst_structure_id_get_value (structure, field_id);
1934   if (other == NULL)
1935     return FALSE;
1936   return gst_value_compare (value, other) == GST_VALUE_EQUAL;
1937 }
1938
1939 static gboolean
1940 gst_qtmux_caps_is_subset_full (GstQTMux * qtmux, GstCaps * subset,
1941     GstCaps * superset)
1942 {
1943   GstStructure *sub_s = gst_caps_get_structure (subset, 0);
1944   GstStructure *sup_s = gst_caps_get_structure (superset, 0);
1945
1946   return gst_structure_foreach (sub_s, check_field, sup_s);
1947 }
1948
1949 static gboolean
1950 gst_qt_mux_audio_sink_set_caps (GstPad * pad, GstCaps * caps)
1951 {
1952   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1953   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1954   GstQTPad *qtpad = NULL;
1955   GstStructure *structure;
1956   const gchar *mimetype;
1957   gint rate, channels;
1958   const GValue *value = NULL;
1959   const GstBuffer *codec_data = NULL;
1960   GstQTMuxFormat format;
1961   AudioSampleEntry entry = { 0, };
1962   AtomInfo *ext_atom = NULL;
1963   gint constant_size = 0;
1964   const gchar *stream_format;
1965   GstCaps *current_caps = NULL;
1966
1967   /* find stream data */
1968   qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
1969   g_assert (qtpad);
1970
1971   qtpad->prepare_buf_func = NULL;
1972
1973   /* does not go well to renegotiate stream mid-way, unless
1974    * the old caps are a subset of the new one (this means upstream
1975    * added more info to the caps, as both should be 'fixed' caps) */
1976   if (qtpad->fourcc) {
1977     g_object_get (pad, "caps", &current_caps, NULL);
1978     g_assert (caps != NULL);
1979
1980     if (!gst_qtmux_caps_is_subset_full (qtmux, current_caps, caps)) {
1981       goto refuse_renegotiation;
1982     }
1983     GST_DEBUG_OBJECT (qtmux,
1984         "pad %s accepted renegotiation to %" GST_PTR_FORMAT " from %"
1985         GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, GST_PAD_CAPS (pad));
1986   }
1987
1988   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
1989       GST_DEBUG_PAD_NAME (pad), caps);
1990
1991   format = qtmux_klass->format;
1992   structure = gst_caps_get_structure (caps, 0);
1993   mimetype = gst_structure_get_name (structure);
1994
1995   /* common info */
1996   if (!gst_structure_get_int (structure, "channels", &channels) ||
1997       !gst_structure_get_int (structure, "rate", &rate)) {
1998     goto refuse_caps;
1999   }
2000
2001   /* optional */
2002   value = gst_structure_get_value (structure, "codec_data");
2003   if (value != NULL)
2004     codec_data = gst_value_get_buffer (value);
2005
2006   qtpad->is_out_of_order = FALSE;
2007   qtpad->have_dts = FALSE;
2008
2009   /* set common properties */
2010   entry.sample_rate = rate;
2011   entry.channels = channels;
2012   /* default */
2013   entry.sample_size = 16;
2014   /* this is the typical compressed case */
2015   if (format == GST_QT_MUX_FORMAT_QT) {
2016     entry.version = 1;
2017     entry.compression_id = -2;
2018   }
2019
2020   /* now map onto a fourcc, and some extra properties */
2021   if (strcmp (mimetype, "audio/mpeg") == 0) {
2022     gint mpegversion = 0;
2023     gint layer = -1;
2024
2025     gst_structure_get_int (structure, "mpegversion", &mpegversion);
2026     switch (mpegversion) {
2027       case 1:
2028         gst_structure_get_int (structure, "layer", &layer);
2029         switch (layer) {
2030           case 3:
2031             /* mp3 */
2032             /* note: QuickTime player does not like mp3 either way in iso/mp4 */
2033             if (format == GST_QT_MUX_FORMAT_QT)
2034               entry.fourcc = FOURCC__mp3;
2035             else {
2036               entry.fourcc = FOURCC_mp4a;
2037               ext_atom =
2038                   build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG1_P3,
2039                   ESDS_STREAM_TYPE_AUDIO, codec_data, qtpad->avg_bitrate,
2040                   qtpad->max_bitrate);
2041             }
2042             entry.samples_per_packet = 1152;
2043             entry.bytes_per_sample = 2;
2044             break;
2045         }
2046         break;
2047       case 4:
2048
2049         /* check stream-format */
2050         stream_format = gst_structure_get_string (structure, "stream-format");
2051         if (stream_format) {
2052           if (strcmp (stream_format, "raw") != 0) {
2053             GST_WARNING_OBJECT (qtmux, "Unsupported AAC stream-format %s, "
2054                 "please use 'raw'", stream_format);
2055             goto refuse_caps;
2056           }
2057         } else {
2058           GST_WARNING_OBJECT (qtmux, "No stream-format present in caps, "
2059               "assuming 'raw'");
2060         }
2061
2062         if (!codec_data || GST_BUFFER_SIZE (codec_data) < 2)
2063           GST_WARNING_OBJECT (qtmux, "no (valid) codec_data for AAC audio");
2064         else {
2065           guint8 profile = GST_READ_UINT8 (GST_BUFFER_DATA (codec_data));
2066
2067           /* warn if not Low Complexity profile */
2068           profile >>= 3;
2069           if (profile != 2)
2070             GST_WARNING_OBJECT (qtmux,
2071                 "non-LC AAC may not run well on (Apple) QuickTime/iTunes");
2072         }
2073
2074         /* AAC */
2075         entry.fourcc = FOURCC_mp4a;
2076
2077         if (format == GST_QT_MUX_FORMAT_QT)
2078           ext_atom = build_mov_aac_extension (qtpad->trak, codec_data,
2079               qtpad->avg_bitrate, qtpad->max_bitrate);
2080         else
2081           ext_atom =
2082               build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P3,
2083               ESDS_STREAM_TYPE_AUDIO, codec_data, qtpad->avg_bitrate,
2084               qtpad->max_bitrate);
2085         break;
2086       default:
2087         break;
2088     }
2089   } else if (strcmp (mimetype, "audio/AMR") == 0) {
2090     entry.fourcc = FOURCC_samr;
2091     entry.sample_size = 16;
2092     entry.samples_per_packet = 160;
2093     entry.bytes_per_sample = 2;
2094     ext_atom = build_amr_extension ();
2095   } else if (strcmp (mimetype, "audio/AMR-WB") == 0) {
2096     entry.fourcc = FOURCC_sawb;
2097     entry.sample_size = 16;
2098     entry.samples_per_packet = 320;
2099     entry.bytes_per_sample = 2;
2100     ext_atom = build_amr_extension ();
2101   } else if (strcmp (mimetype, "audio/x-raw-int") == 0) {
2102     gint width;
2103     gint depth;
2104     gint endianness;
2105     gboolean sign;
2106
2107     if (!gst_structure_get_int (structure, "width", &width) ||
2108         !gst_structure_get_int (structure, "depth", &depth) ||
2109         !gst_structure_get_boolean (structure, "signed", &sign)) {
2110       GST_DEBUG_OBJECT (qtmux, "broken caps, width/depth/signed field missing");
2111       goto refuse_caps;
2112     }
2113
2114     if (depth <= 8) {
2115       endianness = G_BYTE_ORDER;
2116     } else if (!gst_structure_get_int (structure, "endianness", &endianness)) {
2117       GST_DEBUG_OBJECT (qtmux, "broken caps, endianness field missing");
2118       goto refuse_caps;
2119     }
2120
2121     /* spec has no place for a distinction in these */
2122     if (width != depth) {
2123       GST_DEBUG_OBJECT (qtmux, "width must be same as depth!");
2124       goto refuse_caps;
2125     }
2126
2127     if (sign) {
2128       if (endianness == G_LITTLE_ENDIAN)
2129         entry.fourcc = FOURCC_sowt;
2130       else if (endianness == G_BIG_ENDIAN)
2131         entry.fourcc = FOURCC_twos;
2132       /* maximum backward compatibility; only new version for > 16 bit */
2133       if (depth <= 16)
2134         entry.version = 0;
2135       /* not compressed in any case */
2136       entry.compression_id = 0;
2137       /* QT spec says: max at 16 bit even if sample size were actually larger,
2138        * however, most players (e.g. QuickTime!) seem to disagree, so ... */
2139       entry.sample_size = depth;
2140       entry.bytes_per_sample = depth / 8;
2141       entry.samples_per_packet = 1;
2142       entry.bytes_per_packet = depth / 8;
2143       entry.bytes_per_frame = entry.bytes_per_packet * channels;
2144     } else {
2145       if (width == 8 && depth == 8) {
2146         /* fall back to old 8-bit version */
2147         entry.fourcc = FOURCC_raw_;
2148         entry.version = 0;
2149         entry.compression_id = 0;
2150         entry.sample_size = 8;
2151       } else {
2152         GST_DEBUG_OBJECT (qtmux, "non 8-bit PCM must be signed");
2153         goto refuse_caps;
2154       }
2155     }
2156     constant_size = (depth / 8) * channels;
2157   } else if (strcmp (mimetype, "audio/x-alaw") == 0) {
2158     entry.fourcc = FOURCC_alaw;
2159     entry.samples_per_packet = 1023;
2160     entry.bytes_per_sample = 2;
2161   } else if (strcmp (mimetype, "audio/x-mulaw") == 0) {
2162     entry.fourcc = FOURCC_ulaw;
2163     entry.samples_per_packet = 1023;
2164     entry.bytes_per_sample = 2;
2165   } else if (strcmp (mimetype, "audio/x-adpcm") == 0) {
2166     gint blocksize;
2167     if (!gst_structure_get_int (structure, "block_align", &blocksize)) {
2168       GST_DEBUG_OBJECT (qtmux, "broken caps, block_align missing");
2169       goto refuse_caps;
2170     }
2171     /* Currently only supports WAV-style IMA ADPCM, for which the codec id is
2172        0x11 */
2173     entry.fourcc = MS_WAVE_FOURCC (0x11);
2174     /* 4 byte header per channel (including one sample). 2 samples per byte
2175        remaining. Simplifying gives the following (samples per block per
2176        channel) */
2177     entry.samples_per_packet = 2 * blocksize / channels - 7;
2178     entry.bytes_per_sample = 2;
2179
2180     entry.bytes_per_frame = blocksize;
2181     entry.bytes_per_packet = blocksize / channels;
2182     /* ADPCM has constant size packets */
2183     constant_size = 1;
2184     /* TODO: I don't really understand why this helps, but it does! Constant
2185      * size and compression_id of -2 seem to be incompatible, and other files
2186      * in the wild use this too. */
2187     entry.compression_id = -1;
2188
2189     ext_atom = build_ima_adpcm_extension (channels, rate, blocksize);
2190   } else if (strcmp (mimetype, "audio/x-alac") == 0) {
2191     GstBuffer *codec_config;
2192     gint len;
2193
2194     entry.fourcc = FOURCC_alac;
2195     /* let's check if codec data already comes with 'alac' atom prefix */
2196     if (!codec_data || (len = GST_BUFFER_SIZE (codec_data)) < 28) {
2197       GST_DEBUG_OBJECT (qtmux, "broken caps, codec data missing");
2198       goto refuse_caps;
2199     }
2200     if (GST_READ_UINT32_LE (GST_BUFFER_DATA (codec_data) + 4) == FOURCC_alac) {
2201       len -= 8;
2202       codec_config = gst_buffer_create_sub ((GstBuffer *) codec_data, 8, len);
2203     } else {
2204       codec_config = gst_buffer_ref ((GstBuffer *) codec_data);
2205     }
2206     if (len != 28) {
2207       /* does not look good, but perhaps some trailing unneeded stuff */
2208       GST_WARNING_OBJECT (qtmux, "unexpected codec-data size, possibly broken");
2209     }
2210     if (format == GST_QT_MUX_FORMAT_QT)
2211       ext_atom = build_mov_alac_extension (qtpad->trak, codec_config);
2212     else
2213       ext_atom = build_codec_data_extension (FOURCC_alac, codec_config);
2214     /* set some more info */
2215     entry.bytes_per_sample = 2;
2216     entry.samples_per_packet =
2217         GST_READ_UINT32_BE (GST_BUFFER_DATA (codec_config) + 4);
2218     gst_buffer_unref (codec_config);
2219   }
2220
2221   if (!entry.fourcc)
2222     goto refuse_caps;
2223
2224   /* ok, set the pad info accordingly */
2225   qtpad->fourcc = entry.fourcc;
2226   qtpad->sample_size = constant_size;
2227   atom_trak_set_audio_type (qtpad->trak, qtmux->context, &entry,
2228       entry.sample_rate, ext_atom, constant_size);
2229
2230   gst_object_unref (qtmux);
2231   return TRUE;
2232
2233   /* ERRORS */
2234 refuse_caps:
2235   {
2236     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
2237         GST_PAD_NAME (pad), caps);
2238     gst_object_unref (qtmux);
2239     return FALSE;
2240   }
2241 refuse_renegotiation:
2242   {
2243     GST_WARNING_OBJECT (qtmux,
2244         "pad %s refused renegotiation to %" GST_PTR_FORMAT,
2245         GST_PAD_NAME (pad), caps);
2246     gst_object_unref (qtmux);
2247     return FALSE;
2248   }
2249 }
2250
2251 /* scale rate up or down by factor of 10 to fit into [1000,10000] interval */
2252 static guint32
2253 adjust_rate (guint64 rate)
2254 {
2255   if (rate == 0)
2256     return 10000;
2257
2258   while (rate >= 10000)
2259     rate /= 10;
2260
2261   while (rate < 1000)
2262     rate *= 10;
2263
2264   return (guint32) rate;
2265 }
2266
2267 static gboolean
2268 gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps)
2269 {
2270   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
2271   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
2272   GstQTPad *qtpad = NULL;
2273   GstStructure *structure;
2274   const gchar *mimetype;
2275   gint width, height, depth = -1;
2276   gint framerate_num, framerate_den;
2277   guint32 rate;
2278   const GValue *value = NULL;
2279   const GstBuffer *codec_data = NULL;
2280   VisualSampleEntry entry = { 0, };
2281   GstQTMuxFormat format;
2282   AtomInfo *ext_atom = NULL;
2283   GList *ext_atom_list = NULL;
2284   gboolean sync = FALSE;
2285   int par_num, par_den;
2286   GstCaps *current_caps = NULL;
2287
2288   /* find stream data */
2289   qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
2290   g_assert (qtpad);
2291
2292   qtpad->prepare_buf_func = NULL;
2293
2294   /* does not go well to renegotiate stream mid-way, unless
2295    * the old caps are a subset of the new one (this means upstream
2296    * added more info to the caps, as both should be 'fixed' caps) */
2297   if (qtpad->fourcc) {
2298     g_object_get (pad, "caps", &current_caps, NULL);
2299     g_assert (caps != NULL);
2300
2301     if (!gst_qtmux_caps_is_subset_full (qtmux, current_caps, caps)) {
2302       goto refuse_renegotiation;
2303     }
2304     GST_DEBUG_OBJECT (qtmux,
2305         "pad %s accepted renegotiation to %" GST_PTR_FORMAT " from %"
2306         GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, GST_PAD_CAPS (pad));
2307   }
2308
2309   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
2310       GST_DEBUG_PAD_NAME (pad), caps);
2311
2312   format = qtmux_klass->format;
2313   structure = gst_caps_get_structure (caps, 0);
2314   mimetype = gst_structure_get_name (structure);
2315
2316   /* required parts */
2317   if (!gst_structure_get_int (structure, "width", &width) ||
2318       !gst_structure_get_int (structure, "height", &height))
2319     goto refuse_caps;
2320
2321   /* optional */
2322   depth = -1;
2323   /* works as a default timebase */
2324   framerate_num = 10000;
2325   framerate_den = 1;
2326   gst_structure_get_fraction (structure, "framerate", &framerate_num,
2327       &framerate_den);
2328   gst_structure_get_int (structure, "depth", &depth);
2329   value = gst_structure_get_value (structure, "codec_data");
2330   if (value != NULL)
2331     codec_data = gst_value_get_buffer (value);
2332
2333   par_num = 1;
2334   par_den = 1;
2335   gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_num,
2336       &par_den);
2337
2338   qtpad->is_out_of_order = FALSE;
2339
2340   /* bring frame numerator into a range that ensures both reasonable resolution
2341    * as well as a fair duration */
2342   rate = adjust_rate (framerate_num);
2343   GST_DEBUG_OBJECT (qtmux, "Rate of video track selected: %" G_GUINT32_FORMAT,
2344       rate);
2345
2346   /* set common properties */
2347   entry.width = width;
2348   entry.height = height;
2349   entry.par_n = par_num;
2350   entry.par_d = par_den;
2351   /* should be OK according to qt and iso spec, override if really needed */
2352   entry.color_table_id = -1;
2353   entry.frame_count = 1;
2354   entry.depth = 24;
2355
2356   /* sync entries by default */
2357   sync = TRUE;
2358
2359   /* now map onto a fourcc, and some extra properties */
2360   if (strcmp (mimetype, "video/x-raw-rgb") == 0) {
2361     gint bpp;
2362
2363     entry.fourcc = FOURCC_raw_;
2364     gst_structure_get_int (structure, "bpp", &bpp);
2365     entry.depth = bpp;
2366     sync = FALSE;
2367   } else if (strcmp (mimetype, "video/x-raw-yuv") == 0) {
2368     guint32 format = 0;
2369
2370     sync = FALSE;
2371     gst_structure_get_fourcc (structure, "format", &format);
2372     switch (format) {
2373       case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
2374         if (depth == -1)
2375           depth = 24;
2376         entry.fourcc = FOURCC_2vuy;
2377         entry.depth = depth;
2378         break;
2379     }
2380   } else if (strcmp (mimetype, "video/x-h263") == 0) {
2381     ext_atom = NULL;
2382     if (format == GST_QT_MUX_FORMAT_QT)
2383       entry.fourcc = FOURCC_h263;
2384     else
2385       entry.fourcc = FOURCC_s263;
2386     ext_atom = build_h263_extension ();
2387     if (ext_atom != NULL)
2388       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
2389   } else if (strcmp (mimetype, "video/x-divx") == 0 ||
2390       strcmp (mimetype, "video/mpeg") == 0) {
2391     gint version = 0;
2392
2393     if (strcmp (mimetype, "video/x-divx") == 0) {
2394       gst_structure_get_int (structure, "divxversion", &version);
2395       version = version == 5 ? 1 : 0;
2396     } else {
2397       gst_structure_get_int (structure, "mpegversion", &version);
2398       version = version == 4 ? 1 : 0;
2399     }
2400     if (version) {
2401       entry.fourcc = FOURCC_mp4v;
2402       ext_atom =
2403           build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P2,
2404           ESDS_STREAM_TYPE_VISUAL, codec_data, qtpad->avg_bitrate,
2405           qtpad->max_bitrate);
2406       if (ext_atom != NULL)
2407         ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
2408       if (!codec_data)
2409         GST_WARNING_OBJECT (qtmux, "no codec_data for MPEG4 video; "
2410             "output might not play in Apple QuickTime (try global-headers?)");
2411     }
2412   } else if (strcmp (mimetype, "video/x-h264") == 0) {
2413     entry.fourcc = FOURCC_avc1;
2414     if (qtpad->avg_bitrate == 0) {
2415       gint avg_bitrate = 0;
2416       gst_structure_get_int (structure, "bitrate", &avg_bitrate);
2417       qtpad->avg_bitrate = avg_bitrate;
2418     }
2419     ext_atom = build_btrt_extension (0, qtpad->avg_bitrate, qtpad->max_bitrate);
2420     if (ext_atom != NULL)
2421       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
2422     if (!codec_data)
2423       GST_WARNING_OBJECT (qtmux, "no codec_data in h264 caps");
2424     ext_atom = build_codec_data_extension (FOURCC_avcC, codec_data);
2425     if (ext_atom != NULL)
2426       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
2427   } else if (strcmp (mimetype, "video/x-svq") == 0) {
2428     gint version = 0;
2429     const GstBuffer *seqh = NULL;
2430     const GValue *seqh_value;
2431     gdouble gamma = 0;
2432
2433     gst_structure_get_int (structure, "svqversion", &version);
2434     if (version == 3) {
2435       entry.fourcc = FOURCC_SVQ3;
2436       entry.version = 3;
2437       entry.depth = 32;
2438
2439       seqh_value = gst_structure_get_value (structure, "seqh");
2440       if (seqh_value) {
2441         seqh = gst_value_get_buffer (seqh_value);
2442         ext_atom = build_SMI_atom (seqh);
2443         if (ext_atom)
2444           ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
2445       }
2446
2447       /* we need to add the gamma anyway because quicktime might crash
2448        * when it doesn't find it */
2449       if (!gst_structure_get_double (structure, "applied-gamma", &gamma)) {
2450         /* it seems that using 0 here makes it ignored */
2451         gamma = 0.0;
2452       }
2453       ext_atom = build_gama_atom (gamma);
2454       if (ext_atom)
2455         ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
2456     } else {
2457       GST_WARNING_OBJECT (qtmux, "SVQ version %d not supported. Please file "
2458           "a bug at http://bugzilla.gnome.org", version);
2459     }
2460   } else if (strcmp (mimetype, "video/x-dv") == 0) {
2461     gint version = 0;
2462     gboolean pal = TRUE;
2463
2464     sync = FALSE;
2465     if (framerate_num != 25 || framerate_den != 1)
2466       pal = FALSE;
2467     gst_structure_get_int (structure, "dvversion", &version);
2468     /* fall back to typical one */
2469     if (!version)
2470       version = 25;
2471     switch (version) {
2472       case 25:
2473         if (pal)
2474           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', 'p');
2475         else
2476           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', ' ');
2477         break;
2478       case 50:
2479         if (pal)
2480           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'p');
2481         else
2482           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'n');
2483         break;
2484       default:
2485         GST_WARNING_OBJECT (qtmux, "unrecognized dv version");
2486         break;
2487     }
2488   } else if (strcmp (mimetype, "image/jpeg") == 0) {
2489     entry.fourcc = FOURCC_jpeg;
2490     sync = FALSE;
2491   } else if (strcmp (mimetype, "image/x-j2c") == 0 ||
2492       strcmp (mimetype, "image/x-jpc") == 0) {
2493     guint32 fourcc;
2494     const GValue *cmap_array;
2495     const GValue *cdef_array;
2496     gint ncomp = 0;
2497     gint fields = 1;
2498
2499     if (strcmp (mimetype, "image/x-jpc") == 0) {
2500       qtpad->prepare_buf_func = gst_qt_mux_prepare_jpc_buffer;
2501     }
2502
2503     gst_structure_get_int (structure, "num-components", &ncomp);
2504     gst_structure_get_int (structure, "fields", &fields);
2505     cmap_array = gst_structure_get_value (structure, "component-map");
2506     cdef_array = gst_structure_get_value (structure, "channel-definitions");
2507
2508     ext_atom = NULL;
2509     entry.fourcc = FOURCC_mjp2;
2510     sync = FALSE;
2511     if (gst_structure_get_fourcc (structure, "fourcc", &fourcc) &&
2512         (ext_atom =
2513             build_jp2h_extension (qtpad->trak, width, height, fourcc, ncomp,
2514                 cmap_array, cdef_array)) != NULL) {
2515       ext_atom_list = g_list_append (ext_atom_list, ext_atom);
2516
2517       ext_atom = build_fiel_extension (fields);
2518       if (ext_atom)
2519         ext_atom_list = g_list_append (ext_atom_list, ext_atom);
2520
2521       ext_atom = build_jp2x_extension (codec_data);
2522       if (ext_atom)
2523         ext_atom_list = g_list_append (ext_atom_list, ext_atom);
2524     } else {
2525       GST_DEBUG_OBJECT (qtmux, "missing or invalid fourcc in jp2 caps");
2526       goto refuse_caps;
2527     }
2528   } else if (strcmp (mimetype, "video/x-vp8") == 0) {
2529     entry.fourcc = FOURCC_VP80;
2530     sync = FALSE;
2531   } else if (strcmp (mimetype, "video/x-qt-part") == 0) {
2532     guint32 fourcc;
2533
2534     gst_structure_get_fourcc (structure, "format", &fourcc);
2535     entry.fourcc = fourcc;
2536     qtpad->have_dts = TRUE;
2537   } else if (strcmp (mimetype, "video/x-mp4-part") == 0) {
2538     guint32 fourcc;
2539
2540     gst_structure_get_fourcc (structure, "format", &fourcc);
2541     entry.fourcc = fourcc;
2542     qtpad->have_dts = TRUE;
2543   }
2544
2545   if (!entry.fourcc)
2546     goto refuse_caps;
2547
2548   /* ok, set the pad info accordingly */
2549   qtpad->fourcc = entry.fourcc;
2550   qtpad->sync = sync;
2551   atom_trak_set_video_type (qtpad->trak, qtmux->context, &entry, rate,
2552       ext_atom_list);
2553
2554   gst_object_unref (qtmux);
2555   return TRUE;
2556
2557   /* ERRORS */
2558 refuse_caps:
2559   {
2560     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
2561         GST_PAD_NAME (pad), caps);
2562     gst_object_unref (qtmux);
2563     return FALSE;
2564   }
2565 refuse_renegotiation:
2566   {
2567     GST_WARNING_OBJECT (qtmux,
2568         "pad %s refused renegotiation to %" GST_PTR_FORMAT " from %"
2569         GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, GST_PAD_CAPS (pad));
2570     gst_object_unref (qtmux);
2571     return FALSE;
2572   }
2573 }
2574
2575 static gboolean
2576 gst_qt_mux_sink_event (GstPad * pad, GstEvent * event)
2577 {
2578   gboolean ret;
2579   GstQTMux *qtmux;
2580   guint32 avg_bitrate = 0, max_bitrate = 0;
2581
2582   qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
2583   switch (GST_EVENT_TYPE (event)) {
2584     case GST_EVENT_TAG:{
2585       GstTagList *list;
2586       GstTagSetter *setter = GST_TAG_SETTER (qtmux);
2587       GstTagMergeMode mode;
2588
2589       GST_OBJECT_LOCK (qtmux);
2590       mode = gst_tag_setter_get_tag_merge_mode (setter);
2591
2592       GST_DEBUG_OBJECT (qtmux, "received tag event");
2593       gst_event_parse_tag (event, &list);
2594
2595       gst_tag_setter_merge_tags (setter, list, mode);
2596       GST_OBJECT_UNLOCK (qtmux);
2597
2598       if (gst_tag_list_get_uint (list, GST_TAG_BITRATE, &avg_bitrate) |
2599           gst_tag_list_get_uint (list, GST_TAG_MAXIMUM_BITRATE, &max_bitrate)) {
2600         GstQTPad *qtpad = gst_pad_get_element_private (pad);
2601         g_assert (qtpad);
2602
2603         if (avg_bitrate > 0 && avg_bitrate < G_MAXUINT32)
2604           qtpad->avg_bitrate = avg_bitrate;
2605         if (max_bitrate > 0 && max_bitrate < G_MAXUINT32)
2606           qtpad->max_bitrate = max_bitrate;
2607       }
2608
2609       break;
2610     }
2611     default:
2612       break;
2613   }
2614
2615   ret = qtmux->collect_event (pad, event);
2616   gst_object_unref (qtmux);
2617
2618   return ret;
2619 }
2620
2621 static void
2622 gst_qt_mux_release_pad (GstElement * element, GstPad * pad)
2623 {
2624   GstQTMux *mux = GST_QT_MUX_CAST (element);
2625   GSList *walk;
2626
2627   GST_DEBUG_OBJECT (element, "Releasing %s:%s", GST_DEBUG_PAD_NAME (pad));
2628
2629   for (walk = mux->sinkpads; walk; walk = g_slist_next (walk)) {
2630     GstQTPad *qtpad = (GstQTPad *) walk->data;
2631     GST_DEBUG ("Checking %s:%s", GST_DEBUG_PAD_NAME (qtpad->collect.pad));
2632     if (qtpad->collect.pad == pad) {
2633       /* this is it, remove */
2634       mux->sinkpads = g_slist_delete_link (mux->sinkpads, walk);
2635       gst_element_remove_pad (element, pad);
2636       break;
2637     }
2638   }
2639
2640   gst_collect_pads_remove_pad (mux->collect, pad);
2641 }
2642
2643 static GstPad *
2644 gst_qt_mux_request_new_pad (GstElement * element,
2645     GstPadTemplate * templ, const gchar * req_name)
2646 {
2647   GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
2648   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
2649   GstQTPad *collect_pad;
2650   GstPad *newpad;
2651   gboolean audio;
2652   gchar *name;
2653
2654   if (templ->direction != GST_PAD_SINK)
2655     goto wrong_direction;
2656
2657   if (qtmux->state > GST_QT_MUX_STATE_STARTED)
2658     goto too_late;
2659
2660   if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
2661     audio = TRUE;
2662     name = g_strdup_printf ("audio_%02d", qtmux->audio_pads++);
2663   } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
2664     audio = FALSE;
2665     name = g_strdup_printf ("video_%02d", qtmux->video_pads++);
2666   } else
2667     goto wrong_template;
2668
2669   GST_DEBUG_OBJECT (qtmux, "Requested pad: %s", name);
2670
2671   /* create pad and add to collections */
2672   newpad = gst_pad_new_from_template (templ, name);
2673   g_free (name);
2674   collect_pad = (GstQTPad *)
2675       gst_collect_pads_add_pad_full (qtmux->collect, newpad, sizeof (GstQTPad),
2676       (GstCollectDataDestroyNotify) (gst_qt_mux_pad_reset));
2677   /* set up pad */
2678   gst_qt_mux_pad_reset (collect_pad);
2679   collect_pad->trak = atom_trak_new (qtmux->context);
2680   atom_moov_add_trak (qtmux->moov, collect_pad->trak);
2681   qtmux->sinkpads = g_slist_append (qtmux->sinkpads, collect_pad);
2682
2683   /* set up pad functions */
2684   if (audio)
2685     gst_pad_set_setcaps_function (newpad,
2686         GST_DEBUG_FUNCPTR (gst_qt_mux_audio_sink_set_caps));
2687   else
2688     gst_pad_set_setcaps_function (newpad,
2689         GST_DEBUG_FUNCPTR (gst_qt_mux_video_sink_set_caps));
2690
2691   /* FIXME: hacked way to override/extend the event function of
2692    * GstCollectPads; because it sets its own event function giving the
2693    * element no access to events.
2694    */
2695   qtmux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
2696   gst_pad_set_event_function (newpad,
2697       GST_DEBUG_FUNCPTR (gst_qt_mux_sink_event));
2698
2699   gst_pad_set_active (newpad, TRUE);
2700   gst_element_add_pad (element, newpad);
2701
2702   return newpad;
2703
2704   /* ERRORS */
2705 wrong_direction:
2706   {
2707     GST_WARNING_OBJECT (qtmux, "Request pad that is not a SINK pad.");
2708     return NULL;
2709   }
2710 too_late:
2711   {
2712     GST_WARNING_OBJECT (qtmux, "Not providing request pad after stream start.");
2713     return NULL;
2714   }
2715 wrong_template:
2716   {
2717     GST_WARNING_OBJECT (qtmux, "This is not our template!");
2718     return NULL;
2719   }
2720 }
2721
2722 static void
2723 gst_qt_mux_get_property (GObject * object,
2724     guint prop_id, GValue * value, GParamSpec * pspec)
2725 {
2726   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
2727
2728   GST_OBJECT_LOCK (qtmux);
2729   switch (prop_id) {
2730     case PROP_LARGE_FILE:
2731       g_value_set_boolean (value, qtmux->large_file);
2732       break;
2733     case PROP_MOVIE_TIMESCALE:
2734       g_value_set_uint (value, qtmux->timescale);
2735       break;
2736     case PROP_DO_CTTS:
2737       g_value_set_boolean (value, qtmux->guess_pts);
2738       break;
2739     case PROP_FAST_START:
2740       g_value_set_boolean (value, qtmux->fast_start);
2741       break;
2742     case PROP_FAST_START_TEMP_FILE:
2743       g_value_set_string (value, qtmux->fast_start_file_path);
2744       break;
2745     case PROP_MOOV_RECOV_FILE:
2746       g_value_set_string (value, qtmux->moov_recov_file_path);
2747       break;
2748     default:
2749       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2750       break;
2751   }
2752   GST_OBJECT_UNLOCK (qtmux);
2753 }
2754
2755 static void
2756 gst_qt_mux_generate_fast_start_file_path (GstQTMux * qtmux)
2757 {
2758   gchar *tmp;
2759
2760   g_free (qtmux->fast_start_file_path);
2761   qtmux->fast_start_file_path = NULL;
2762
2763   tmp = g_strdup_printf ("%s%d", "qtmux", g_random_int ());
2764   qtmux->fast_start_file_path = g_build_filename (g_get_tmp_dir (), tmp, NULL);
2765   g_free (tmp);
2766 }
2767
2768 static void
2769 gst_qt_mux_set_property (GObject * object,
2770     guint prop_id, const GValue * value, GParamSpec * pspec)
2771 {
2772   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
2773
2774   GST_OBJECT_LOCK (qtmux);
2775   switch (prop_id) {
2776     case PROP_LARGE_FILE:
2777       qtmux->large_file = g_value_get_boolean (value);
2778       break;
2779     case PROP_MOVIE_TIMESCALE:
2780       qtmux->timescale = g_value_get_uint (value);
2781       break;
2782     case PROP_DO_CTTS:
2783       qtmux->guess_pts = g_value_get_boolean (value);
2784       break;
2785     case PROP_FAST_START:
2786       qtmux->fast_start = g_value_get_boolean (value);
2787       break;
2788     case PROP_FAST_START_TEMP_FILE:
2789       g_free (qtmux->fast_start_file_path);
2790       qtmux->fast_start_file_path = g_value_dup_string (value);
2791       /* NULL means to generate a random one */
2792       if (!qtmux->fast_start_file_path) {
2793         gst_qt_mux_generate_fast_start_file_path (qtmux);
2794       }
2795       break;
2796     case PROP_MOOV_RECOV_FILE:
2797       g_free (qtmux->moov_recov_file_path);
2798       qtmux->moov_recov_file_path = g_value_dup_string (value);
2799       break;
2800     default:
2801       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2802       break;
2803   }
2804   GST_OBJECT_UNLOCK (qtmux);
2805 }
2806
2807 static GstStateChangeReturn
2808 gst_qt_mux_change_state (GstElement * element, GstStateChange transition)
2809 {
2810   GstStateChangeReturn ret;
2811   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
2812
2813   switch (transition) {
2814     case GST_STATE_CHANGE_NULL_TO_READY:
2815       break;
2816     case GST_STATE_CHANGE_READY_TO_PAUSED:
2817       gst_collect_pads_start (qtmux->collect);
2818       qtmux->state = GST_QT_MUX_STATE_STARTED;
2819       break;
2820     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
2821       break;
2822     case GST_STATE_CHANGE_PAUSED_TO_READY:
2823       gst_collect_pads_stop (qtmux->collect);
2824       break;
2825     default:
2826       break;
2827   }
2828
2829   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2830
2831   switch (transition) {
2832     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
2833       break;
2834     case GST_STATE_CHANGE_PAUSED_TO_READY:
2835       gst_qt_mux_reset (qtmux, TRUE);
2836       break;
2837     case GST_STATE_CHANGE_READY_TO_NULL:
2838       break;
2839     default:
2840       break;
2841   }
2842
2843   return ret;
2844 }
2845
2846 gboolean
2847 gst_qt_mux_register (GstPlugin * plugin)
2848 {
2849   GTypeInfo typeinfo = {
2850     sizeof (GstQTMuxClass),
2851     (GBaseInitFunc) gst_qt_mux_base_init,
2852     NULL,
2853     (GClassInitFunc) gst_qt_mux_class_init,
2854     NULL,
2855     NULL,
2856     sizeof (GstQTMux),
2857     0,
2858     (GInstanceInitFunc) gst_qt_mux_init,
2859   };
2860   static const GInterfaceInfo tag_setter_info = {
2861     NULL, NULL, NULL
2862   };
2863   GType type;
2864   GstQTMuxFormat format;
2865   GstQTMuxClassParams *params;
2866   guint i = 0;
2867
2868   GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
2869
2870   GST_LOG ("Registering muxers");
2871
2872   while (TRUE) {
2873     GstQTMuxFormatProp *prop;
2874
2875     prop = &gst_qt_mux_format_list[i];
2876     format = prop->format;
2877     if (format == GST_QT_MUX_FORMAT_NONE)
2878       break;
2879
2880     /* create a cache for these properties */
2881     params = g_new0 (GstQTMuxClassParams, 1);
2882     params->prop = prop;
2883     params->src_caps = gst_static_caps_get (&prop->src_caps);
2884     params->video_sink_caps = gst_static_caps_get (&prop->video_sink_caps);
2885     params->audio_sink_caps = gst_static_caps_get (&prop->audio_sink_caps);
2886
2887     /* create the type now */
2888     type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo,
2889         0);
2890     g_type_set_qdata (type, GST_QT_MUX_PARAMS_QDATA, (gpointer) params);
2891     g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
2892
2893     if (!gst_element_register (plugin, prop->name, GST_RANK_PRIMARY, type))
2894       return FALSE;
2895
2896     i++;
2897   }
2898
2899   GST_LOG ("Finished registering muxers");
2900
2901   /* FIXME: ideally classification tag should be added and
2902      registered in gstreamer core gsttaglist
2903    */
2904
2905   GST_LOG ("Registering tags");
2906
2907   gst_tag_register (GST_TAG_3GP_CLASSIFICATION, GST_TAG_FLAG_META,
2908       G_TYPE_STRING, GST_TAG_3GP_CLASSIFICATION, "content classification",
2909       gst_tag_merge_use_first);
2910
2911   GST_LOG ("Finished registering tags");
2912
2913   return TRUE;
2914 }