isomp4/qtmux: Write correct file duration when gaps exist.
[platform/upstream/gst-plugins-good.git] / gst / isomp4 / 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  * Copyright (C) 2010 Nokia Corporation. All rights reserved.
5  * Contact: Stefan Kost <stefan.kost@nokia.com>
6
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 /*
23  * Unless otherwise indicated, Source Code is licensed under MIT license.
24  * See further explanation attached in License Statement (distributed in the file
25  * LICENSE).
26  *
27  * Permission is hereby granted, free of charge, to any person obtaining a copy of
28  * this software and associated documentation files (the "Software"), to deal in
29  * the Software without restriction, including without limitation the rights to
30  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
31  * of the Software, and to permit persons to whom the Software is furnished to do
32  * so, subject to the following conditions:
33  *
34  * The above copyright notice and this permission notice shall be included in all
35  * copies or substantial portions of the Software.
36  *
37  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
38  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
39  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
40  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
41  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
42  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
43  * SOFTWARE.
44  */
45
46
47 /**
48  * SECTION:element-qtmux
49  * @short_description: Muxer for quicktime(.mov) files
50  *
51  * This element merges streams (audio and video) into QuickTime(.mov) files.
52  *
53  * The following background intends to explain why various similar muxers
54  * are present in this plugin.
55  *
56  * The <ulink url="http://www.apple.com/quicktime/resources/qtfileformat.pdf">
57  * QuickTime file format specification</ulink> served as basis for the MP4 file
58  * format specification (mp4mux), and as such the QuickTime file structure is
59  * nearly identical to the so-called ISO Base Media file format defined in
60  * ISO 14496-12 (except for some media specific parts).
61  * In turn, the latter ISO Base Media format was further specialized as a
62  * Motion JPEG-2000 file format in ISO 15444-3 (mj2mux)
63  * and in various 3GPP(2) specs (gppmux).
64  * The fragmented file features defined (only) in ISO Base Media are used by
65  * ISMV files making up (a.o.) Smooth Streaming (ismlmux).
66  *
67  * A few properties (#GstQTMux:movie-timescale, #GstQTMux:trak-timescale) allow
68  * adjusting some technical parameters, which might be useful in (rare) cases to
69  * resolve compatibility issues in some situations.
70  *
71  * Some other properties influence the result more fundamentally.
72  * A typical mov/mp4 file's metadata (aka moov) is located at the end of the
73  * file, somewhat contrary to this usually being called "the header".
74  * However, a #GstQTMux:faststart file will (with some effort) arrange this to
75  * be located near start of the file, which then allows it e.g. to be played
76  * while downloading. Alternatively, rather than having one chunk of metadata at
77  * start (or end), there can be some metadata at start and most of the other
78  * data can be spread out into fragments of #GstQTMux:fragment-duration.
79  * If such fragmented layout is intended for streaming purposes, then
80  * #GstQTMux:streamable allows foregoing to add index metadata (at the end of
81  * file).
82  *
83  * <refsect2>
84  * <title>Example pipelines</title>
85  * |[
86  * gst-launch-1.0 v4l2src num-buffers=500 ! video/x-raw,width=320,height=240 ! videoconvert ! qtmux ! filesink location=video.mov
87  * ]|
88  * Records a video stream captured from a v4l2 device and muxes it into a qt file.
89  * </refsect2>
90  */
91
92 /*
93  * Based on avimux
94  */
95
96 #ifdef HAVE_CONFIG_H
97 #include "config.h"
98 #endif
99
100 #include <glib/gstdio.h>
101
102 #include <gst/gst.h>
103 #include <gst/base/gstcollectpads.h>
104 #include <gst/audio/audio.h>
105 #include <gst/video/video.h>
106 #include <gst/tag/tag.h>
107
108 #include <sys/types.h>
109 #ifdef G_OS_WIN32
110 #include <io.h>                 /* lseek, open, close, read */
111 #undef lseek
112 #define lseek _lseeki64
113 #undef off_t
114 #define off_t guint64
115 #endif
116
117 #ifdef _MSC_VER
118 #define ftruncate g_win32_ftruncate
119 #endif
120
121 #ifdef HAVE_UNISTD_H
122 #  include <unistd.h>
123 #endif
124
125 #include "gstqtmux.h"
126
127 GST_DEBUG_CATEGORY_STATIC (gst_qt_mux_debug);
128 #define GST_CAT_DEFAULT gst_qt_mux_debug
129
130 #ifndef GST_REMOVE_DEPRECATED
131 enum
132 {
133   DTS_METHOD_DD,
134   DTS_METHOD_REORDER,
135   DTS_METHOD_ASC
136 };
137
138 static GType
139 gst_qt_mux_dts_method_get_type (void)
140 {
141   static GType gst_qt_mux_dts_method = 0;
142
143   if (!gst_qt_mux_dts_method) {
144     static const GEnumValue dts_methods[] = {
145       {DTS_METHOD_DD, "delta/duration", "dd"},
146       {DTS_METHOD_REORDER, "reorder", "reorder"},
147       {DTS_METHOD_ASC, "ascending", "asc"},
148       {0, NULL, NULL},
149     };
150
151     gst_qt_mux_dts_method =
152         g_enum_register_static ("GstQTMuxDtsMethods", dts_methods);
153   }
154
155   return gst_qt_mux_dts_method;
156 }
157
158 #define GST_TYPE_QT_MUX_DTS_METHOD \
159   (gst_qt_mux_dts_method_get_type ())
160 #endif
161
162 /* QTMux signals and args */
163 enum
164 {
165   /* FILL ME */
166   LAST_SIGNAL
167 };
168
169 enum
170 {
171   PROP_0,
172   PROP_MOVIE_TIMESCALE,
173   PROP_TRAK_TIMESCALE,
174   PROP_FAST_START,
175   PROP_FAST_START_TEMP_FILE,
176   PROP_MOOV_RECOV_FILE,
177   PROP_FRAGMENT_DURATION,
178   PROP_STREAMABLE,
179 #ifndef GST_REMOVE_DEPRECATED
180   PROP_DTS_METHOD,
181 #endif
182   PROP_DO_CTTS,
183 };
184
185 /* some spare for header size as well */
186 #define MDAT_LARGE_FILE_LIMIT           ((guint64) 1024 * 1024 * 1024 * 2)
187
188 #define DEFAULT_MOVIE_TIMESCALE         1000
189 #define DEFAULT_TRAK_TIMESCALE          0
190 #define DEFAULT_DO_CTTS                 TRUE
191 #define DEFAULT_FAST_START              FALSE
192 #define DEFAULT_FAST_START_TEMP_FILE    NULL
193 #define DEFAULT_MOOV_RECOV_FILE         NULL
194 #define DEFAULT_FRAGMENT_DURATION       0
195 #define DEFAULT_STREAMABLE              TRUE
196 #ifndef GST_REMOVE_DEPRECATED
197 #define DEFAULT_DTS_METHOD              DTS_METHOD_REORDER
198 #endif
199
200
201 static void gst_qt_mux_finalize (GObject * object);
202
203 static GstStateChangeReturn gst_qt_mux_change_state (GstElement * element,
204     GstStateChange transition);
205
206 /* property functions */
207 static void gst_qt_mux_set_property (GObject * object,
208     guint prop_id, const GValue * value, GParamSpec * pspec);
209 static void gst_qt_mux_get_property (GObject * object,
210     guint prop_id, GValue * value, GParamSpec * pspec);
211
212 /* pad functions */
213 static GstPad *gst_qt_mux_request_new_pad (GstElement * element,
214     GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
215 static void gst_qt_mux_release_pad (GstElement * element, GstPad * pad);
216
217 /* event */
218 static gboolean gst_qt_mux_sink_event (GstCollectPads * pads,
219     GstCollectData * data, GstEvent * event, gpointer user_data);
220
221 static GstFlowReturn gst_qt_mux_handle_buffer (GstCollectPads * pads,
222     GstCollectData * cdata, GstBuffer * buf, gpointer user_data);
223 static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
224     GstBuffer * buf);
225
226 static GstElementClass *parent_class = NULL;
227
228 static void
229 gst_qt_mux_base_init (gpointer g_class)
230 {
231   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
232   GstQTMuxClass *klass = (GstQTMuxClass *) g_class;
233   GstQTMuxClassParams *params;
234   GstPadTemplate *videosinktempl, *audiosinktempl, *subtitlesinktempl;
235   GstPadTemplate *srctempl;
236   gchar *longname, *description;
237
238   params =
239       (GstQTMuxClassParams *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (g_class),
240       GST_QT_MUX_PARAMS_QDATA);
241   g_assert (params != NULL);
242
243   /* construct the element details struct */
244   longname = g_strdup_printf ("%s Muxer", params->prop->long_name);
245   description = g_strdup_printf ("Multiplex audio and video into a %s file",
246       params->prop->long_name);
247   gst_element_class_set_static_metadata (element_class, longname,
248       "Codec/Muxer", description,
249       "Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>");
250   g_free (longname);
251   g_free (description);
252
253   /* pad templates */
254   srctempl = gst_pad_template_new ("src", GST_PAD_SRC,
255       GST_PAD_ALWAYS, params->src_caps);
256   gst_element_class_add_pad_template (element_class, srctempl);
257
258   if (params->audio_sink_caps) {
259     audiosinktempl = gst_pad_template_new ("audio_%u",
260         GST_PAD_SINK, GST_PAD_REQUEST, params->audio_sink_caps);
261     gst_element_class_add_pad_template (element_class, audiosinktempl);
262   }
263
264   if (params->video_sink_caps) {
265     videosinktempl = gst_pad_template_new ("video_%u",
266         GST_PAD_SINK, GST_PAD_REQUEST, params->video_sink_caps);
267     gst_element_class_add_pad_template (element_class, videosinktempl);
268   }
269
270   if (params->subtitle_sink_caps) {
271     subtitlesinktempl = gst_pad_template_new ("subtitle_%u",
272         GST_PAD_SINK, GST_PAD_REQUEST, params->subtitle_sink_caps);
273     gst_element_class_add_pad_template (element_class, subtitlesinktempl);
274   }
275
276   klass->format = params->prop->format;
277 }
278
279 static void
280 gst_qt_mux_class_init (GstQTMuxClass * klass)
281 {
282   GObjectClass *gobject_class;
283   GstElementClass *gstelement_class;
284   const gchar *streamable_desc;
285   gboolean streamable;
286 #define STREAMABLE_DESC "If set to true, the output should be as if it is to "\
287   "be streamed and hence no indexes written or duration written."
288
289   gobject_class = (GObjectClass *) klass;
290   gstelement_class = (GstElementClass *) klass;
291
292   parent_class = g_type_class_peek_parent (klass);
293
294   gobject_class->finalize = gst_qt_mux_finalize;
295   gobject_class->get_property = gst_qt_mux_get_property;
296   gobject_class->set_property = gst_qt_mux_set_property;
297
298   if (klass->format == GST_QT_MUX_FORMAT_ISML) {
299     streamable_desc = STREAMABLE_DESC;
300     streamable = DEFAULT_STREAMABLE;
301   } else {
302     streamable_desc =
303         STREAMABLE_DESC " (DEPRECATED, only valid for fragmented MP4)";
304     streamable = FALSE;
305   }
306
307   g_object_class_install_property (gobject_class, PROP_MOVIE_TIMESCALE,
308       g_param_spec_uint ("movie-timescale", "Movie timescale",
309           "Timescale to use in the movie (units per second)",
310           1, G_MAXUINT32, DEFAULT_MOVIE_TIMESCALE,
311           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
312   g_object_class_install_property (gobject_class, PROP_TRAK_TIMESCALE,
313       g_param_spec_uint ("trak-timescale", "Track timescale",
314           "Timescale to use for the tracks (units per second, 0 is automatic)",
315           0, G_MAXUINT32, DEFAULT_TRAK_TIMESCALE,
316           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
317   g_object_class_install_property (gobject_class, PROP_DO_CTTS,
318       g_param_spec_boolean ("presentation-time",
319           "Include presentation-time info",
320           "Calculate and include presentation/composition time "
321           "(in addition to decoding time)", DEFAULT_DO_CTTS,
322           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
323 #ifndef GST_REMOVE_DEPRECATED
324   g_object_class_install_property (gobject_class, PROP_DTS_METHOD,
325       g_param_spec_enum ("dts-method", "dts-method",
326           "(DEPRECATED) Method to determine DTS time",
327           GST_TYPE_QT_MUX_DTS_METHOD, DEFAULT_DTS_METHOD,
328           G_PARAM_DEPRECATED | G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
329           G_PARAM_STATIC_STRINGS));
330 #endif
331   g_object_class_install_property (gobject_class, PROP_FAST_START,
332       g_param_spec_boolean ("faststart", "Format file to faststart",
333           "If the file should be formatted for faststart (headers first)",
334           DEFAULT_FAST_START, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
335   g_object_class_install_property (gobject_class, PROP_FAST_START_TEMP_FILE,
336       g_param_spec_string ("faststart-file", "File to use for storing buffers",
337           "File that will be used temporarily to store data from the stream "
338           "when creating a faststart file. If null a filepath will be "
339           "created automatically", DEFAULT_FAST_START_TEMP_FILE,
340           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
341   g_object_class_install_property (gobject_class, PROP_MOOV_RECOV_FILE,
342       g_param_spec_string ("moov-recovery-file",
343           "File to store data for posterior moov atom recovery",
344           "File to be used to store "
345           "data for moov atom making movie file recovery possible in case "
346           "of a crash during muxing. Null for disabled. (Experimental)",
347           DEFAULT_MOOV_RECOV_FILE,
348           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
349   g_object_class_install_property (gobject_class, PROP_FRAGMENT_DURATION,
350       g_param_spec_uint ("fragment-duration", "Fragment duration",
351           "Fragment durations in ms (produce a fragmented file if > 0)",
352           0, G_MAXUINT32, klass->format == GST_QT_MUX_FORMAT_ISML ?
353           2000 : DEFAULT_FRAGMENT_DURATION,
354           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
355   g_object_class_install_property (gobject_class, PROP_STREAMABLE,
356       g_param_spec_boolean ("streamable", "Streamable", streamable_desc,
357           streamable,
358           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
359
360   gstelement_class->request_new_pad =
361       GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
362   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qt_mux_change_state);
363   gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_release_pad);
364 }
365
366 static void
367 gst_qt_mux_pad_reset (GstQTPad * qtpad)
368 {
369   qtpad->fourcc = 0;
370   qtpad->is_out_of_order = FALSE;
371   qtpad->sample_size = 0;
372   qtpad->sync = FALSE;
373   qtpad->last_dts = 0;
374   qtpad->first_ts = GST_CLOCK_TIME_NONE;
375   qtpad->prepare_buf_func = NULL;
376   qtpad->create_empty_buffer = NULL;
377   qtpad->avg_bitrate = 0;
378   qtpad->max_bitrate = 0;
379   qtpad->total_duration = 0;
380   qtpad->total_bytes = 0;
381   qtpad->sparse = FALSE;
382
383   qtpad->buf_head = 0;
384   qtpad->buf_tail = 0;
385
386   if (qtpad->last_buf)
387     gst_buffer_replace (&qtpad->last_buf, NULL);
388
389   /* reference owned elsewhere */
390   qtpad->trak = NULL;
391
392   if (qtpad->traf) {
393     atom_traf_free (qtpad->traf);
394     qtpad->traf = NULL;
395   }
396   atom_array_clear (&qtpad->fragment_buffers);
397
398   /* reference owned elsewhere */
399   qtpad->tfra = NULL;
400 }
401
402 /*
403  * Takes GstQTMux back to its initial state
404  */
405 static void
406 gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
407 {
408   GSList *walk;
409
410   qtmux->state = GST_QT_MUX_STATE_NONE;
411   qtmux->header_size = 0;
412   qtmux->mdat_size = 0;
413   qtmux->mdat_pos = 0;
414   qtmux->longest_chunk = GST_CLOCK_TIME_NONE;
415   qtmux->video_pads = 0;
416   qtmux->audio_pads = 0;
417   qtmux->fragment_sequence = 0;
418
419   if (qtmux->ftyp) {
420     atom_ftyp_free (qtmux->ftyp);
421     qtmux->ftyp = NULL;
422   }
423   if (qtmux->moov) {
424     atom_moov_free (qtmux->moov);
425     qtmux->moov = NULL;
426   }
427   if (qtmux->mfra) {
428     atom_mfra_free (qtmux->mfra);
429     qtmux->mfra = NULL;
430   }
431   if (qtmux->fast_start_file) {
432     fclose (qtmux->fast_start_file);
433     g_remove (qtmux->fast_start_file_path);
434     qtmux->fast_start_file = NULL;
435   }
436   if (qtmux->moov_recov_file) {
437     fclose (qtmux->moov_recov_file);
438     qtmux->moov_recov_file = NULL;
439   }
440   for (walk = qtmux->extra_atoms; walk; walk = g_slist_next (walk)) {
441     AtomInfo *ainfo = (AtomInfo *) walk->data;
442     ainfo->free_func (ainfo->atom);
443     g_free (ainfo);
444   }
445   g_slist_free (qtmux->extra_atoms);
446   qtmux->extra_atoms = NULL;
447
448   GST_OBJECT_LOCK (qtmux);
449   gst_tag_setter_reset_tags (GST_TAG_SETTER (qtmux));
450   GST_OBJECT_UNLOCK (qtmux);
451
452   /* reset pad data */
453   for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
454     GstQTPad *qtpad = (GstQTPad *) walk->data;
455     gst_qt_mux_pad_reset (qtpad);
456
457     /* hm, moov_free above yanked the traks away from us,
458      * so do not free, but do clear */
459     qtpad->trak = NULL;
460   }
461
462   if (alloc) {
463     qtmux->moov = atom_moov_new (qtmux->context);
464     /* ensure all is as nice and fresh as request_new_pad would provide it */
465     for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
466       GstQTPad *qtpad = (GstQTPad *) walk->data;
467
468       qtpad->trak = atom_trak_new (qtmux->context);
469       atom_moov_add_trak (qtmux->moov, qtpad->trak);
470     }
471   }
472 }
473
474 static void
475 gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
476 {
477   GstElementClass *klass = GST_ELEMENT_CLASS (qtmux_klass);
478   GstPadTemplate *templ;
479
480   templ = gst_element_class_get_pad_template (klass, "src");
481   qtmux->srcpad = gst_pad_new_from_template (templ, "src");
482   gst_pad_use_fixed_caps (qtmux->srcpad);
483   gst_element_add_pad (GST_ELEMENT (qtmux), qtmux->srcpad);
484
485   qtmux->sinkpads = NULL;
486   qtmux->collect = gst_collect_pads_new ();
487   gst_collect_pads_set_buffer_function (qtmux->collect,
488       GST_DEBUG_FUNCPTR (gst_qt_mux_handle_buffer), qtmux);
489   gst_collect_pads_set_event_function (qtmux->collect,
490       GST_DEBUG_FUNCPTR (gst_qt_mux_sink_event), qtmux);
491   gst_collect_pads_set_clip_function (qtmux->collect,
492       GST_DEBUG_FUNCPTR (gst_collect_pads_clip_running_time), qtmux);
493
494   /* properties set to default upon construction */
495
496   /* always need this */
497   qtmux->context =
498       atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format));
499
500   /* internals to initial state */
501   gst_qt_mux_reset (qtmux, TRUE);
502 }
503
504
505 static void
506 gst_qt_mux_finalize (GObject * object)
507 {
508   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
509
510   gst_qt_mux_reset (qtmux, FALSE);
511
512   g_free (qtmux->fast_start_file_path);
513   g_free (qtmux->moov_recov_file_path);
514
515   atoms_context_free (qtmux->context);
516   gst_object_unref (qtmux->collect);
517
518   g_slist_free (qtmux->sinkpads);
519
520   G_OBJECT_CLASS (parent_class)->finalize (object);
521 }
522
523 static GstBuffer *
524 gst_qt_mux_prepare_jpc_buffer (GstQTPad * qtpad, GstBuffer * buf,
525     GstQTMux * qtmux)
526 {
527   GstBuffer *newbuf;
528   GstMapInfo map;
529   gsize size;
530
531   GST_LOG_OBJECT (qtmux, "Preparing jpc buffer");
532
533   if (buf == NULL)
534     return NULL;
535
536   size = gst_buffer_get_size (buf);
537   newbuf = gst_buffer_new_and_alloc (size + 8);
538   gst_buffer_copy_into (newbuf, buf, GST_BUFFER_COPY_ALL, 8, size);
539
540   gst_buffer_map (newbuf, &map, GST_MAP_WRITE);
541   GST_WRITE_UINT32_BE (map.data, map.size);
542   GST_WRITE_UINT32_LE (map.data + 4, FOURCC_jp2c);
543
544   gst_buffer_unmap (buf, &map);
545   gst_buffer_unref (buf);
546
547   return newbuf;
548 }
549
550 static GstBuffer *
551 gst_qt_mux_prepare_tx3g_buffer (GstQTPad * qtpad, GstBuffer * buf,
552     GstQTMux * qtmux)
553 {
554   GstBuffer *newbuf;
555   GstMapInfo frommap;
556   GstMapInfo tomap;
557   gsize size;
558
559   GST_LOG_OBJECT (qtmux, "Preparing tx3g buffer %" GST_PTR_FORMAT, buf);
560
561   if (buf == NULL)
562     return NULL;
563
564   size = gst_buffer_get_size (buf);
565   newbuf = gst_buffer_new_and_alloc (size + 2);
566
567   gst_buffer_map (buf, &frommap, GST_MAP_READ);
568   gst_buffer_map (newbuf, &tomap, GST_MAP_WRITE);
569
570   GST_WRITE_UINT16_BE (tomap.data, size);
571   memcpy (tomap.data + 2, frommap.data, size);
572
573   gst_buffer_unmap (newbuf, &tomap);
574   gst_buffer_unmap (buf, &frommap);
575
576   gst_buffer_copy_into (newbuf, buf, GST_BUFFER_COPY_METADATA, 0, size);
577
578   gst_buffer_unref (buf);
579
580   return newbuf;
581 }
582
583 static GstBuffer *
584 gst_qt_mux_create_empty_tx3g_buffer (GstQTPad * qtpad, gint64 duration)
585 {
586   guint8 *data;
587
588   data = g_malloc (2);
589   GST_WRITE_UINT16_BE (data, 0);
590
591   return gst_buffer_new_wrapped (data, 2);;
592 }
593
594 static void
595 gst_qt_mux_add_mp4_tag (GstQTMux * qtmux, const GstTagList * list,
596     const char *tag, const char *tag2, guint32 fourcc)
597 {
598   switch (gst_tag_get_type (tag)) {
599       /* strings */
600     case G_TYPE_STRING:
601     {
602       gchar *str = NULL;
603
604       if (!gst_tag_list_get_string (list, tag, &str) || !str)
605         break;
606       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
607           GST_FOURCC_ARGS (fourcc), str);
608       atom_moov_add_str_tag (qtmux->moov, fourcc, str);
609       g_free (str);
610       break;
611     }
612       /* double */
613     case G_TYPE_DOUBLE:
614     {
615       gdouble value;
616
617       if (!gst_tag_list_get_double (list, tag, &value))
618         break;
619       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
620           GST_FOURCC_ARGS (fourcc), (gint) value);
621       atom_moov_add_uint_tag (qtmux->moov, fourcc, 21, (gint) value);
622       break;
623     }
624     case G_TYPE_UINT:
625     {
626       guint value = 0;
627       if (tag2) {
628         /* paired unsigned integers */
629         guint count = 0;
630         gboolean got_tag;
631
632         got_tag = gst_tag_list_get_uint (list, tag, &value);
633         got_tag = gst_tag_list_get_uint (list, tag2, &count) || got_tag;
634         if (!got_tag)
635           break;
636         GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u",
637             GST_FOURCC_ARGS (fourcc), value, count);
638         atom_moov_add_uint_tag (qtmux->moov, fourcc, 0,
639             value << 16 | (count & 0xFFFF));
640       } else {
641         /* unpaired unsigned integers */
642         if (!gst_tag_list_get_uint (list, tag, &value))
643           break;
644         GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
645             GST_FOURCC_ARGS (fourcc), value);
646         atom_moov_add_uint_tag (qtmux->moov, fourcc, 1, value);
647       }
648       break;
649     }
650     default:
651       g_assert_not_reached ();
652       break;
653   }
654 }
655
656 static void
657 gst_qt_mux_add_mp4_date (GstQTMux * qtmux, const GstTagList * list,
658     const char *tag, const char *tag2, guint32 fourcc)
659 {
660   GDate *date = NULL;
661   GDateYear year;
662   GDateMonth month;
663   GDateDay day;
664   gchar *str;
665
666   g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_DATE);
667
668   if (!gst_tag_list_get_date (list, tag, &date) || !date)
669     return;
670
671   year = g_date_get_year (date);
672   month = g_date_get_month (date);
673   day = g_date_get_day (date);
674
675   g_date_free (date);
676
677   if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH &&
678       day == G_DATE_BAD_DAY) {
679     GST_WARNING_OBJECT (qtmux, "invalid date in tag");
680     return;
681   }
682
683   str = g_strdup_printf ("%u-%u-%u", year, month, day);
684   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
685       GST_FOURCC_ARGS (fourcc), str);
686   atom_moov_add_str_tag (qtmux->moov, fourcc, str);
687   g_free (str);
688 }
689
690 static void
691 gst_qt_mux_add_mp4_cover (GstQTMux * qtmux, const GstTagList * list,
692     const char *tag, const char *tag2, guint32 fourcc)
693 {
694   GValue value = { 0, };
695   GstBuffer *buf;
696   GstSample *sample;
697   GstCaps *caps;
698   GstStructure *structure;
699   gint flags = 0;
700   GstMapInfo map;
701
702   g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_SAMPLE);
703
704   if (!gst_tag_list_copy_value (&value, list, tag))
705     return;
706
707   sample = gst_value_get_sample (&value);
708
709   if (!sample)
710     goto done;
711
712   buf = gst_sample_get_buffer (sample);
713   if (!buf)
714     goto done;
715
716   caps = gst_sample_get_caps (sample);
717   if (!caps) {
718     GST_WARNING_OBJECT (qtmux, "preview image without caps");
719     goto done;
720   }
721
722   GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);
723
724   structure = gst_caps_get_structure (caps, 0);
725   if (gst_structure_has_name (structure, "image/jpeg"))
726     flags = 13;
727   else if (gst_structure_has_name (structure, "image/png"))
728     flags = 14;
729
730   if (!flags) {
731     GST_WARNING_OBJECT (qtmux, "preview image format not supported");
732     goto done;
733   }
734
735   gst_buffer_map (buf, &map, GST_MAP_READ);
736   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT
737       " -> image size %" G_GSIZE_FORMAT "", GST_FOURCC_ARGS (fourcc), map.size);
738   atom_moov_add_tag (qtmux->moov, fourcc, flags, map.data, map.size);
739   gst_buffer_unmap (buf, &map);
740 done:
741   g_value_unset (&value);
742 }
743
744 static void
745 gst_qt_mux_add_3gp_str (GstQTMux * qtmux, const GstTagList * list,
746     const char *tag, const char *tag2, guint32 fourcc)
747 {
748   gchar *str = NULL;
749   guint number;
750
751   g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_STRING);
752   g_return_if_fail (!tag2 || gst_tag_get_type (tag2) == G_TYPE_UINT);
753
754   if (!gst_tag_list_get_string (list, tag, &str) || !str)
755     return;
756
757   if (tag2)
758     if (!gst_tag_list_get_uint (list, tag2, &number))
759       tag2 = NULL;
760
761   if (!tag2) {
762     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
763         GST_FOURCC_ARGS (fourcc), str);
764     atom_moov_add_3gp_str_tag (qtmux->moov, fourcc, str);
765   } else {
766     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s/%d",
767         GST_FOURCC_ARGS (fourcc), str, number);
768     atom_moov_add_3gp_str_int_tag (qtmux->moov, fourcc, str, number);
769   }
770
771   g_free (str);
772 }
773
774 static void
775 gst_qt_mux_add_3gp_date (GstQTMux * qtmux, const GstTagList * list,
776     const char *tag, const char *tag2, guint32 fourcc)
777 {
778   GDate *date = NULL;
779   GDateYear year;
780
781   g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_DATE);
782
783   if (!gst_tag_list_get_date (list, tag, &date) || !date)
784     return;
785
786   year = g_date_get_year (date);
787
788   if (year == G_DATE_BAD_YEAR) {
789     GST_WARNING_OBJECT (qtmux, "invalid date in tag");
790     return;
791   }
792
793   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %d",
794       GST_FOURCC_ARGS (fourcc), year);
795   atom_moov_add_3gp_uint_tag (qtmux->moov, fourcc, year);
796 }
797
798 static void
799 gst_qt_mux_add_3gp_location (GstQTMux * qtmux, const GstTagList * list,
800     const char *tag, const char *tag2, guint32 fourcc)
801 {
802   gdouble latitude = -360, longitude = -360, altitude = 0;
803   gchar *location = NULL;
804   guint8 *data, *ddata;
805   gint size = 0, len = 0;
806   gboolean ret = FALSE;
807
808   g_return_if_fail (strcmp (tag, GST_TAG_GEO_LOCATION_NAME) == 0);
809
810   ret = gst_tag_list_get_string (list, tag, &location);
811   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LONGITUDE,
812       &longitude);
813   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LATITUDE,
814       &latitude);
815   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_ELEVATION,
816       &altitude);
817
818   if (!ret)
819     return;
820
821   if (location)
822     len = strlen (location);
823   size += len + 1 + 2;
824
825   /* role + (long, lat, alt) + body + notes */
826   size += 1 + 3 * 4 + 1 + 1;
827
828   data = ddata = g_malloc (size);
829
830   /* language tag */
831   GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
832   /* location */
833   if (location)
834     memcpy (data + 2, location, len);
835   GST_WRITE_UINT8 (data + 2 + len, 0);
836   data += len + 1 + 2;
837   /* role */
838   GST_WRITE_UINT8 (data, 0);
839   /* long, lat, alt */
840 #define QT_WRITE_SFP32(data, fp) GST_WRITE_UINT32_BE(data, (guint32) ((gint) (fp * 65536.0)))
841   QT_WRITE_SFP32 (data + 1, longitude);
842   QT_WRITE_SFP32 (data + 5, latitude);
843   QT_WRITE_SFP32 (data + 9, altitude);
844   /* neither astronomical body nor notes */
845   GST_WRITE_UINT16_BE (data + 13, 0);
846
847   GST_DEBUG_OBJECT (qtmux, "Adding tag 'loci'");
848   atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
849   g_free (ddata);
850 }
851
852 static void
853 gst_qt_mux_add_3gp_keywords (GstQTMux * qtmux, const GstTagList * list,
854     const char *tag, const char *tag2, guint32 fourcc)
855 {
856   gchar *keywords = NULL;
857   guint8 *data, *ddata;
858   gint size = 0, i;
859   gchar **kwds;
860
861   g_return_if_fail (strcmp (tag, GST_TAG_KEYWORDS) == 0);
862
863   if (!gst_tag_list_get_string (list, tag, &keywords) || !keywords)
864     return;
865
866   kwds = g_strsplit (keywords, ",", 0);
867   g_free (keywords);
868
869   size = 0;
870   for (i = 0; kwds[i]; i++) {
871     /* size byte + null-terminator */
872     size += strlen (kwds[i]) + 1 + 1;
873   }
874
875   /* language tag + count + keywords */
876   size += 2 + 1;
877
878   data = ddata = g_malloc (size);
879
880   /* language tag */
881   GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
882   /* count */
883   GST_WRITE_UINT8 (data + 2, i);
884   data += 3;
885   /* keywords */
886   for (i = 0; kwds[i]; ++i) {
887     gint len = strlen (kwds[i]);
888
889     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
890         GST_FOURCC_ARGS (fourcc), kwds[i]);
891     /* size */
892     GST_WRITE_UINT8 (data, len + 1);
893     memcpy (data + 1, kwds[i], len + 1);
894     data += len + 2;
895   }
896
897   g_strfreev (kwds);
898
899   atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
900   g_free (ddata);
901 }
902
903 static gboolean
904 gst_qt_mux_parse_classification_string (GstQTMux * qtmux, const gchar * input,
905     guint32 * p_fourcc, guint16 * p_table, gchar ** p_content)
906 {
907   guint32 fourcc;
908   gint table;
909   gint size;
910   const gchar *data;
911
912   data = input;
913   size = strlen (input);
914
915   if (size < 4 + 3 + 1 + 1 + 1) {
916     /* at least the minimum xxxx://y/z */
917     GST_WARNING_OBJECT (qtmux, "Classification tag input (%s) too short, "
918         "ignoring", input);
919     return FALSE;
920   }
921
922   /* read the fourcc */
923   memcpy (&fourcc, data, 4);
924   size -= 4;
925   data += 4;
926
927   if (strncmp (data, "://", 3) != 0) {
928     goto mismatch;
929   }
930   data += 3;
931   size -= 3;
932
933   /* read the table number */
934   if (sscanf (data, "%d", &table) != 1) {
935     goto mismatch;
936   }
937   if (table < 0) {
938     GST_WARNING_OBJECT (qtmux, "Invalid table number in classification tag (%d)"
939         ", table numbers should be positive, ignoring tag", table);
940     return FALSE;
941   }
942
943   /* find the next / */
944   while (size > 0 && data[0] != '/') {
945     data += 1;
946     size -= 1;
947   }
948   if (size == 0) {
949     goto mismatch;
950   }
951   g_assert (data[0] == '/');
952
953   /* skip the '/' */
954   data += 1;
955   size -= 1;
956   if (size == 0) {
957     goto mismatch;
958   }
959
960   /* read up the rest of the string */
961   *p_content = g_strdup (data);
962   *p_table = (guint16) table;
963   *p_fourcc = fourcc;
964   return TRUE;
965
966 mismatch:
967   {
968     GST_WARNING_OBJECT (qtmux, "Ignoring classification tag as "
969         "input (%s) didn't match the expected entitycode://table/content",
970         input);
971     return FALSE;
972   }
973 }
974
975 static void
976 gst_qt_mux_add_3gp_classification (GstQTMux * qtmux, const GstTagList * list,
977     const char *tag, const char *tag2, guint32 fourcc)
978 {
979   gchar *clsf_data = NULL;
980   gint size = 0;
981   guint32 entity = 0;
982   guint16 table = 0;
983   gchar *content = NULL;
984   guint8 *data;
985
986   g_return_if_fail (strcmp (tag, GST_TAG_3GP_CLASSIFICATION) == 0);
987
988   if (!gst_tag_list_get_string (list, tag, &clsf_data) || !clsf_data)
989     return;
990
991   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
992       GST_FOURCC_ARGS (fourcc), clsf_data);
993
994   /* parse the string, format is:
995    * entityfourcc://table/content
996    */
997   gst_qt_mux_parse_classification_string (qtmux, clsf_data, &entity, &table,
998       &content);
999   g_free (clsf_data);
1000   /* +1 for the \0 */
1001   size = strlen (content) + 1;
1002
1003   /* now we have everything, build the atom
1004    * atom description is at 3GPP TS 26.244 V8.2.0 (2009-09) */
1005   data = g_malloc (4 + 2 + 2 + size);
1006   GST_WRITE_UINT32_LE (data, entity);
1007   GST_WRITE_UINT16_BE (data + 4, (guint16) table);
1008   GST_WRITE_UINT16_BE (data + 6, 0);
1009   memcpy (data + 8, content, size);
1010   g_free (content);
1011
1012   atom_moov_add_3gp_tag (qtmux->moov, fourcc, data, 4 + 2 + 2 + size);
1013   g_free (data);
1014 }
1015
1016 typedef void (*GstQTMuxAddTagFunc) (GstQTMux * mux, const GstTagList * list,
1017     const char *tag, const char *tag2, guint32 fourcc);
1018
1019 /*
1020  * Struct to record mappings from gstreamer tags to fourcc codes
1021  */
1022 typedef struct _GstTagToFourcc
1023 {
1024   guint32 fourcc;
1025   const gchar *gsttag;
1026   const gchar *gsttag2;
1027   const GstQTMuxAddTagFunc func;
1028 } GstTagToFourcc;
1029
1030 /* tag list tags to fourcc matching */
1031 static const GstTagToFourcc tag_matches_mp4[] = {
1032   {FOURCC__alb, GST_TAG_ALBUM, NULL, gst_qt_mux_add_mp4_tag},
1033   {FOURCC_soal, GST_TAG_ALBUM_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1034   {FOURCC__ART, GST_TAG_ARTIST, NULL, gst_qt_mux_add_mp4_tag},
1035   {FOURCC_soar, GST_TAG_ARTIST_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1036   {FOURCC_aART, GST_TAG_ALBUM_ARTIST, NULL, gst_qt_mux_add_mp4_tag},
1037   {FOURCC_soaa, GST_TAG_ALBUM_ARTIST_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1038   {FOURCC__cmt, GST_TAG_COMMENT, NULL, gst_qt_mux_add_mp4_tag},
1039   {FOURCC__wrt, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_mp4_tag},
1040   {FOURCC_soco, GST_TAG_COMPOSER_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1041   {FOURCC_tvsh, GST_TAG_SHOW_NAME, NULL, gst_qt_mux_add_mp4_tag},
1042   {FOURCC_sosn, GST_TAG_SHOW_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1043   {FOURCC_tvsn, GST_TAG_SHOW_SEASON_NUMBER, NULL, gst_qt_mux_add_mp4_tag},
1044   {FOURCC_tves, GST_TAG_SHOW_EPISODE_NUMBER, NULL, gst_qt_mux_add_mp4_tag},
1045   {FOURCC__gen, GST_TAG_GENRE, NULL, gst_qt_mux_add_mp4_tag},
1046   {FOURCC__nam, GST_TAG_TITLE, NULL, gst_qt_mux_add_mp4_tag},
1047   {FOURCC_sonm, GST_TAG_TITLE_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1048   {FOURCC_perf, GST_TAG_PERFORMER, NULL, gst_qt_mux_add_mp4_tag},
1049   {FOURCC__grp, GST_TAG_GROUPING, NULL, gst_qt_mux_add_mp4_tag},
1050   {FOURCC__des, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_mp4_tag},
1051   {FOURCC__lyr, GST_TAG_LYRICS, NULL, gst_qt_mux_add_mp4_tag},
1052   {FOURCC__too, GST_TAG_ENCODER, NULL, gst_qt_mux_add_mp4_tag},
1053   {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_mp4_tag},
1054   {FOURCC_keyw, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_mp4_tag},
1055   {FOURCC__day, GST_TAG_DATE, NULL, gst_qt_mux_add_mp4_date},
1056   {FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE, NULL, gst_qt_mux_add_mp4_tag},
1057   {FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT,
1058       gst_qt_mux_add_mp4_tag},
1059   {FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT,
1060       gst_qt_mux_add_mp4_tag},
1061   {FOURCC_covr, GST_TAG_PREVIEW_IMAGE, NULL, gst_qt_mux_add_mp4_cover},
1062   {FOURCC_covr, GST_TAG_IMAGE, NULL, gst_qt_mux_add_mp4_cover},
1063   {0, NULL,}
1064 };
1065
1066 static const GstTagToFourcc tag_matches_3gp[] = {
1067   {FOURCC_titl, GST_TAG_TITLE, NULL, gst_qt_mux_add_3gp_str},
1068   {FOURCC_dscp, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_3gp_str},
1069   {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_3gp_str},
1070   {FOURCC_perf, GST_TAG_ARTIST, NULL, gst_qt_mux_add_3gp_str},
1071   {FOURCC_auth, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_3gp_str},
1072   {FOURCC_gnre, GST_TAG_GENRE, NULL, gst_qt_mux_add_3gp_str},
1073   {FOURCC_kywd, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_3gp_keywords},
1074   {FOURCC_yrrc, GST_TAG_DATE, NULL, gst_qt_mux_add_3gp_date},
1075   {FOURCC_albm, GST_TAG_ALBUM, GST_TAG_TRACK_NUMBER, gst_qt_mux_add_3gp_str},
1076   {FOURCC_loci, GST_TAG_GEO_LOCATION_NAME, NULL, gst_qt_mux_add_3gp_location},
1077   {FOURCC_clsf, GST_TAG_3GP_CLASSIFICATION, NULL,
1078       gst_qt_mux_add_3gp_classification},
1079   {0, NULL,}
1080 };
1081
1082 /* qtdemux produces these for atoms it cannot parse */
1083 #define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag"
1084
1085 static void
1086 gst_qt_mux_add_xmp_tags (GstQTMux * qtmux, const GstTagList * list)
1087 {
1088   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1089   GstBuffer *xmp = NULL;
1090
1091   /* adobe specs only have 'quicktime' and 'mp4',
1092    * but I guess we can extrapolate to gpp.
1093    * Keep mj2 out for now as we don't add any tags for it yet.
1094    * If you have further info about xmp on these formats, please share */
1095   if (qtmux_klass->format == GST_QT_MUX_FORMAT_MJ2)
1096     return;
1097
1098   GST_DEBUG_OBJECT (qtmux, "Adding xmp tags");
1099
1100   if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT) {
1101     xmp = gst_tag_xmp_writer_tag_list_to_xmp_buffer (GST_TAG_XMP_WRITER (qtmux),
1102         list, TRUE);
1103     if (xmp)
1104       atom_moov_add_xmp_tags (qtmux->moov, xmp);
1105   } else {
1106     AtomInfo *ainfo;
1107     /* for isom/mp4, it is a top level uuid atom */
1108     xmp = gst_tag_xmp_writer_tag_list_to_xmp_buffer (GST_TAG_XMP_WRITER (qtmux),
1109         list, TRUE);
1110     if (xmp) {
1111       ainfo = build_uuid_xmp_atom (xmp);
1112       if (ainfo) {
1113         qtmux->extra_atoms = g_slist_prepend (qtmux->extra_atoms, ainfo);
1114       }
1115     }
1116   }
1117   if (xmp)
1118     gst_buffer_unref (xmp);
1119 }
1120
1121 static void
1122 gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list)
1123 {
1124   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1125   guint32 fourcc;
1126   gint i;
1127   const gchar *tag, *tag2;
1128   const GstTagToFourcc *tag_matches;
1129
1130   switch (qtmux_klass->format) {
1131     case GST_QT_MUX_FORMAT_3GP:
1132       tag_matches = tag_matches_3gp;
1133       break;
1134     case GST_QT_MUX_FORMAT_MJ2:
1135       tag_matches = NULL;
1136       break;
1137     default:
1138       /* sort of iTunes style for mp4 and QT (?) */
1139       tag_matches = tag_matches_mp4;
1140       break;
1141   }
1142
1143   if (!tag_matches)
1144     return;
1145
1146   for (i = 0; tag_matches[i].fourcc; i++) {
1147     fourcc = tag_matches[i].fourcc;
1148     tag = tag_matches[i].gsttag;
1149     tag2 = tag_matches[i].gsttag2;
1150
1151     g_assert (tag_matches[i].func);
1152     tag_matches[i].func (qtmux, list, tag, tag2, fourcc);
1153   }
1154
1155   /* add unparsed blobs if present */
1156   if (gst_tag_exists (GST_QT_DEMUX_PRIVATE_TAG)) {
1157     guint num_tags;
1158
1159     num_tags = gst_tag_list_get_tag_size (list, GST_QT_DEMUX_PRIVATE_TAG);
1160     for (i = 0; i < num_tags; ++i) {
1161       GstSample *sample = NULL;
1162       GstBuffer *buf;
1163       const GstStructure *s;
1164
1165       if (!gst_tag_list_get_sample_index (list, GST_QT_DEMUX_PRIVATE_TAG, i,
1166               &sample))
1167         continue;
1168       buf = gst_sample_get_buffer (sample);
1169
1170       if (buf && (s = gst_sample_get_info (sample))) {
1171         const gchar *style = NULL;
1172         GstMapInfo map;
1173
1174         gst_buffer_map (buf, &map, GST_MAP_READ);
1175         GST_DEBUG_OBJECT (qtmux,
1176             "Found private tag %d/%d; size %" G_GSIZE_FORMAT ", info %"
1177             GST_PTR_FORMAT, i, num_tags, map.size, s);
1178         if (s && (style = gst_structure_get_string (s, "style"))) {
1179           /* try to prevent some style tag ending up into another variant
1180            * (todo: make into a list if more cases) */
1181           if ((strcmp (style, "itunes") == 0 &&
1182                   qtmux_klass->format == GST_QT_MUX_FORMAT_MP4) ||
1183               (strcmp (style, "iso") == 0 &&
1184                   qtmux_klass->format == GST_QT_MUX_FORMAT_3GP)) {
1185             GST_DEBUG_OBJECT (qtmux, "Adding private tag");
1186             atom_moov_add_blob_tag (qtmux->moov, map.data, map.size);
1187           }
1188         }
1189         gst_buffer_unmap (buf, &map);
1190       }
1191     }
1192   }
1193
1194   return;
1195 }
1196
1197 /*
1198  * Gets the tagsetter iface taglist and puts the known tags
1199  * into the output stream
1200  */
1201 static void
1202 gst_qt_mux_setup_metadata (GstQTMux * qtmux)
1203 {
1204   const GstTagList *tags;
1205
1206   GST_OBJECT_LOCK (qtmux);
1207   tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (qtmux));
1208   GST_OBJECT_UNLOCK (qtmux);
1209
1210   GST_LOG_OBJECT (qtmux, "tags: %" GST_PTR_FORMAT, tags);
1211
1212   if (tags && !gst_tag_list_is_empty (tags)) {
1213     GstTagList *copy = gst_tag_list_copy (tags);
1214
1215     GST_DEBUG_OBJECT (qtmux, "Removing bogus tags");
1216     gst_tag_list_remove_tag (copy, GST_TAG_VIDEO_CODEC);
1217     gst_tag_list_remove_tag (copy, GST_TAG_AUDIO_CODEC);
1218     gst_tag_list_remove_tag (copy, GST_TAG_CONTAINER_FORMAT);
1219
1220     GST_DEBUG_OBJECT (qtmux, "Formatting tags");
1221     gst_qt_mux_add_metadata_tags (qtmux, copy);
1222     gst_qt_mux_add_xmp_tags (qtmux, copy);
1223     gst_tag_list_unref (copy);
1224   } else {
1225     GST_DEBUG_OBJECT (qtmux, "No tags received");
1226   }
1227 }
1228
1229 static inline GstBuffer *
1230 _gst_buffer_new_take_data (guint8 * data, guint size)
1231 {
1232   GstBuffer *buf;
1233
1234   buf = gst_buffer_new ();
1235   gst_buffer_append_memory (buf,
1236       gst_memory_new_wrapped (0, data, size, 0, size, data, g_free));
1237
1238   return buf;
1239 }
1240
1241 static GstFlowReturn
1242 gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset,
1243     gboolean mind_fast)
1244 {
1245   GstFlowReturn res;
1246   gsize size;
1247
1248   g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
1249
1250   size = gst_buffer_get_size (buf);
1251   GST_LOG_OBJECT (qtmux, "sending buffer size %" G_GSIZE_FORMAT, size);
1252
1253   if (mind_fast && qtmux->fast_start_file) {
1254     GstMapInfo map;
1255     gint ret;
1256
1257     GST_LOG_OBJECT (qtmux, "to temporary file");
1258     gst_buffer_map (buf, &map, GST_MAP_READ);
1259     ret = fwrite (map.data, sizeof (guint8), map.size, qtmux->fast_start_file);
1260     gst_buffer_unmap (buf, &map);
1261     gst_buffer_unref (buf);
1262     if (ret != size)
1263       goto write_error;
1264     else
1265       res = GST_FLOW_OK;
1266   } else {
1267     GST_LOG_OBJECT (qtmux, "downstream");
1268     res = gst_pad_push (qtmux->srcpad, buf);
1269   }
1270
1271   if (G_LIKELY (offset))
1272     *offset += size;
1273
1274   return res;
1275
1276   /* ERRORS */
1277 write_error:
1278   {
1279     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
1280         ("Failed to write to temporary file"), GST_ERROR_SYSTEM);
1281     return GST_FLOW_ERROR;
1282   }
1283 }
1284
1285 static gboolean
1286 gst_qt_mux_seek_to_beginning (FILE * f)
1287 {
1288 #ifdef HAVE_FSEEKO
1289   if (fseeko (f, (off_t) 0, SEEK_SET) != 0)
1290     return FALSE;
1291 #elif defined (G_OS_UNIX) || defined (G_OS_WIN32)
1292   if (lseek (fileno (f), (off_t) 0, SEEK_SET) == (off_t) - 1)
1293     return FALSE;
1294 #else
1295   if (fseek (f, (long) 0, SEEK_SET) != 0)
1296     return FALSE;
1297 #endif
1298   return TRUE;
1299 }
1300
1301 static GstFlowReturn
1302 gst_qt_mux_send_buffered_data (GstQTMux * qtmux, guint64 * offset)
1303 {
1304   GstFlowReturn ret = GST_FLOW_OK;
1305   GstBuffer *buf = NULL;
1306
1307   if (fflush (qtmux->fast_start_file))
1308     goto flush_failed;
1309
1310   if (!gst_qt_mux_seek_to_beginning (qtmux->fast_start_file))
1311     goto seek_failed;
1312
1313   /* hm, this could all take a really really long time,
1314    * but there may not be another way to get moov atom first
1315    * (somehow optimize copy?) */
1316   GST_DEBUG_OBJECT (qtmux, "Sending buffered data");
1317   while (ret == GST_FLOW_OK) {
1318     const int bufsize = 4096;
1319     GstMapInfo map;
1320     gsize size;
1321
1322     buf = gst_buffer_new_and_alloc (bufsize);
1323     gst_buffer_map (buf, &map, GST_MAP_WRITE);
1324     size = fread (map.data, sizeof (guint8), bufsize, qtmux->fast_start_file);
1325     if (size == 0) {
1326       gst_buffer_unmap (buf, &map);
1327       break;
1328     }
1329     GST_LOG_OBJECT (qtmux, "Pushing buffered buffer of size %d", (gint) size);
1330     gst_buffer_unmap (buf, &map);
1331     if (size != bufsize)
1332       gst_buffer_set_size (buf, size);
1333     ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
1334     buf = NULL;
1335   }
1336   if (buf)
1337     gst_buffer_unref (buf);
1338
1339   if (ftruncate (fileno (qtmux->fast_start_file), 0))
1340     goto seek_failed;
1341   if (!gst_qt_mux_seek_to_beginning (qtmux->fast_start_file))
1342     goto seek_failed;
1343
1344   return ret;
1345
1346   /* ERRORS */
1347 flush_failed:
1348   {
1349     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
1350         ("Failed to flush temporary file"), GST_ERROR_SYSTEM);
1351     ret = GST_FLOW_ERROR;
1352     goto fail;
1353   }
1354 seek_failed:
1355   {
1356     GST_ELEMENT_ERROR (qtmux, RESOURCE, SEEK,
1357         ("Failed to seek temporary file"), GST_ERROR_SYSTEM);
1358     ret = GST_FLOW_ERROR;
1359     goto fail;
1360   }
1361 fail:
1362   {
1363     /* clear descriptor so we don't remove temp file later on,
1364      * might be possible to recover */
1365     fclose (qtmux->fast_start_file);
1366     qtmux->fast_start_file = NULL;
1367     return ret;
1368   }
1369 }
1370
1371 /*
1372  * Sends the initial mdat atom fields (size fields and fourcc type),
1373  * the subsequent buffers are considered part of it's data.
1374  * As we can't predict the amount of data that we are going to place in mdat
1375  * we need to record the position of the size field in the stream so we can
1376  * seek back to it later and update when the streams have finished.
1377  */
1378 static GstFlowReturn
1379 gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size,
1380     gboolean extended)
1381 {
1382   Atom *node_header;
1383   GstBuffer *buf;
1384   guint8 *data = NULL;
1385   guint64 offset = 0;
1386
1387   GST_DEBUG_OBJECT (qtmux, "Sending mdat's atom header, "
1388       "size %" G_GUINT64_FORMAT, size);
1389
1390   node_header = g_malloc0 (sizeof (Atom));
1391   node_header->type = FOURCC_mdat;
1392   if (extended) {
1393     /* use extended size */
1394     node_header->size = 1;
1395     node_header->extended_size = 0;
1396     if (size)
1397       node_header->extended_size = size + 16;
1398   } else {
1399     node_header->size = size + 8;
1400   }
1401
1402   size = offset = 0;
1403   if (atom_copy_data (node_header, &data, &size, &offset) == 0)
1404     goto serialize_error;
1405
1406   buf = _gst_buffer_new_take_data (data, offset);
1407   g_free (node_header);
1408
1409   GST_LOG_OBJECT (qtmux, "Pushing mdat start");
1410   return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
1411
1412   /* ERRORS */
1413 serialize_error:
1414   {
1415     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1416         ("Failed to serialize mdat"));
1417     g_free (node_header);
1418     return GST_FLOW_ERROR;
1419   }
1420 }
1421
1422 /*
1423  * We get the position of the mdat size field, seek back to it
1424  * and overwrite with the real value
1425  */
1426 static GstFlowReturn
1427 gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos,
1428     guint64 mdat_size, guint64 * offset)
1429 {
1430   GstBuffer *buf;
1431   gboolean large_file;
1432   GstSegment segment;
1433   GstMapInfo map;
1434
1435   large_file = (mdat_size > MDAT_LARGE_FILE_LIMIT);
1436
1437   if (large_file)
1438     mdat_pos += 8;
1439
1440   /* seek and rewrite the header */
1441   gst_segment_init (&segment, GST_FORMAT_BYTES);
1442   segment.start = mdat_pos;
1443   gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
1444
1445   if (large_file) {
1446     buf = gst_buffer_new_and_alloc (sizeof (guint64));
1447     gst_buffer_map (buf, &map, GST_MAP_WRITE);
1448     GST_WRITE_UINT64_BE (map.data, mdat_size + 16);
1449   } else {
1450     buf = gst_buffer_new_and_alloc (16);
1451     gst_buffer_map (buf, &map, GST_MAP_WRITE);
1452     GST_WRITE_UINT32_BE (map.data, 8);
1453     GST_WRITE_UINT32_LE (map.data + 4, FOURCC_free);
1454     GST_WRITE_UINT32_BE (map.data + 8, mdat_size + 8);
1455     GST_WRITE_UINT32_LE (map.data + 12, FOURCC_mdat);
1456   }
1457   gst_buffer_unmap (buf, &map);
1458
1459   return gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
1460 }
1461
1462 static GstFlowReturn
1463 gst_qt_mux_send_ftyp (GstQTMux * qtmux, guint64 * off)
1464 {
1465   GstBuffer *buf;
1466   guint64 size = 0, offset = 0;
1467   guint8 *data = NULL;
1468
1469   GST_DEBUG_OBJECT (qtmux, "Sending ftyp atom");
1470
1471   if (!atom_ftyp_copy_data (qtmux->ftyp, &data, &size, &offset))
1472     goto serialize_error;
1473
1474   buf = _gst_buffer_new_take_data (data, offset);
1475
1476   GST_LOG_OBJECT (qtmux, "Pushing ftyp");
1477   return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
1478
1479   /* ERRORS */
1480 serialize_error:
1481   {
1482     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1483         ("Failed to serialize ftyp"));
1484     return GST_FLOW_ERROR;
1485   }
1486 }
1487
1488 static void
1489 gst_qt_mux_prepare_ftyp (GstQTMux * qtmux, AtomFTYP ** p_ftyp,
1490     GstBuffer ** p_prefix)
1491 {
1492   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1493   guint32 major, version;
1494   GList *comp;
1495   GstBuffer *prefix = NULL;
1496   AtomFTYP *ftyp = NULL;
1497
1498   GST_DEBUG_OBJECT (qtmux, "Preparing ftyp and possible prefix atom");
1499
1500   /* init and send context and ftyp based on current property state */
1501   gst_qt_mux_map_format_to_header (qtmux_klass->format, &prefix, &major,
1502       &version, &comp, qtmux->moov, qtmux->longest_chunk,
1503       qtmux->fast_start_file != NULL);
1504   ftyp = atom_ftyp_new (qtmux->context, major, version, comp);
1505   if (comp)
1506     g_list_free (comp);
1507   if (prefix) {
1508     if (p_prefix)
1509       *p_prefix = prefix;
1510     else
1511       gst_buffer_unref (prefix);
1512   }
1513   *p_ftyp = ftyp;
1514 }
1515
1516 static GstFlowReturn
1517 gst_qt_mux_prepare_and_send_ftyp (GstQTMux * qtmux)
1518 {
1519   GstFlowReturn ret = GST_FLOW_OK;
1520   GstBuffer *prefix = NULL;
1521
1522   GST_DEBUG_OBJECT (qtmux, "Preparing to send ftyp atom");
1523
1524   /* init and send context and ftyp based on current property state */
1525   if (qtmux->ftyp) {
1526     atom_ftyp_free (qtmux->ftyp);
1527     qtmux->ftyp = NULL;
1528   }
1529   gst_qt_mux_prepare_ftyp (qtmux, &qtmux->ftyp, &prefix);
1530   if (prefix) {
1531     ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE);
1532     if (ret != GST_FLOW_OK)
1533       return ret;
1534   }
1535   return gst_qt_mux_send_ftyp (qtmux, &qtmux->header_size);
1536 }
1537
1538 static void
1539 gst_qt_mux_set_header_on_caps (GstQTMux * mux, GstBuffer * buf)
1540 {
1541   GstStructure *structure;
1542   GValue array = { 0 };
1543   GValue value = { 0 };
1544   GstCaps *caps, *tcaps;
1545
1546   tcaps = gst_pad_get_current_caps (mux->srcpad);
1547   caps = gst_caps_copy (tcaps);
1548   gst_caps_unref (tcaps);
1549
1550   structure = gst_caps_get_structure (caps, 0);
1551
1552   g_value_init (&array, GST_TYPE_ARRAY);
1553
1554   GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);
1555   g_value_init (&value, GST_TYPE_BUFFER);
1556   gst_value_take_buffer (&value, gst_buffer_ref (buf));
1557   gst_value_array_append_value (&array, &value);
1558   g_value_unset (&value);
1559
1560   gst_structure_set_value (structure, "streamheader", &array);
1561   g_value_unset (&array);
1562   gst_pad_set_caps (mux->srcpad, caps);
1563   gst_caps_unref (caps);
1564 }
1565
1566 static void
1567 gst_qt_mux_configure_moov (GstQTMux * qtmux, guint32 * _timescale)
1568 {
1569   gboolean fragmented;
1570   guint32 timescale;
1571
1572   GST_OBJECT_LOCK (qtmux);
1573   timescale = qtmux->timescale;
1574   fragmented = qtmux->fragment_sequence > 0;
1575   GST_OBJECT_UNLOCK (qtmux);
1576
1577   /* inform lower layers of our property wishes, and determine duration.
1578    * Let moov take care of this using its list of traks;
1579    * so that released pads are also included */
1580   GST_DEBUG_OBJECT (qtmux, "Updating timescale to %" G_GUINT32_FORMAT,
1581       timescale);
1582   atom_moov_update_timescale (qtmux->moov, timescale);
1583   atom_moov_set_fragmented (qtmux->moov, fragmented);
1584
1585   atom_moov_update_duration (qtmux->moov);
1586
1587   if (_timescale)
1588     *_timescale = timescale;
1589 }
1590
1591 static GstFlowReturn
1592 gst_qt_mux_send_moov (GstQTMux * qtmux, guint64 * _offset, gboolean mind_fast)
1593 {
1594   guint64 offset = 0, size = 0;
1595   guint8 *data;
1596   GstBuffer *buf;
1597   GstFlowReturn ret = GST_FLOW_OK;
1598
1599   /* serialize moov */
1600   offset = size = 0;
1601   data = NULL;
1602   GST_LOG_OBJECT (qtmux, "Copying movie header into buffer");
1603   if (!atom_moov_copy_data (qtmux->moov, &data, &size, &offset))
1604     goto serialize_error;
1605
1606   buf = _gst_buffer_new_take_data (data, offset);
1607   GST_DEBUG_OBJECT (qtmux, "Pushing moov atoms");
1608   gst_qt_mux_set_header_on_caps (qtmux, buf);
1609   ret = gst_qt_mux_send_buffer (qtmux, buf, _offset, mind_fast);
1610
1611   return ret;
1612
1613 serialize_error:
1614   {
1615     g_free (data);
1616     return GST_FLOW_ERROR;
1617   }
1618 }
1619
1620 /* either calculates size of extra atoms or pushes them */
1621 static GstFlowReturn
1622 gst_qt_mux_send_extra_atoms (GstQTMux * qtmux, gboolean send, guint64 * offset,
1623     gboolean mind_fast)
1624 {
1625   GSList *walk;
1626   guint64 loffset = 0, size = 0;
1627   guint8 *data;
1628   GstFlowReturn ret = GST_FLOW_OK;
1629
1630   for (walk = qtmux->extra_atoms; walk; walk = g_slist_next (walk)) {
1631     AtomInfo *ainfo = (AtomInfo *) walk->data;
1632
1633     loffset = size = 0;
1634     data = NULL;
1635     if (!ainfo->copy_data_func (ainfo->atom,
1636             send ? &data : NULL, &size, &loffset))
1637       goto serialize_error;
1638
1639     if (send) {
1640       GstBuffer *buf;
1641
1642       GST_DEBUG_OBJECT (qtmux,
1643           "Pushing extra top-level atom %" GST_FOURCC_FORMAT,
1644           GST_FOURCC_ARGS (ainfo->atom->type));
1645       buf = _gst_buffer_new_take_data (data, loffset);
1646       ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
1647       if (ret != GST_FLOW_OK)
1648         break;
1649     } else {
1650       if (offset)
1651         *offset += loffset;
1652     }
1653   }
1654
1655   return ret;
1656
1657 serialize_error:
1658   {
1659     g_free (data);
1660     return GST_FLOW_ERROR;
1661   }
1662 }
1663
1664 static GstFlowReturn
1665 gst_qt_mux_start_file (GstQTMux * qtmux)
1666 {
1667   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1668   GstFlowReturn ret = GST_FLOW_OK;
1669   GstCaps *caps;
1670   GstSegment segment;
1671   gchar s_id[32];
1672
1673   GST_DEBUG_OBJECT (qtmux, "starting file");
1674
1675   /* stream-start (FIXME: create id based on input ids) */
1676   g_snprintf (s_id, sizeof (s_id), "qtmux-%08x", g_random_int ());
1677   gst_pad_push_event (qtmux->srcpad, gst_event_new_stream_start (s_id));
1678
1679   caps = gst_caps_copy (gst_pad_get_pad_template_caps (qtmux->srcpad));
1680   /* qtmux has structure with and without variant, remove all but the first */
1681   while (gst_caps_get_size (caps) > 1)
1682     gst_caps_remove_structure (caps, 1);
1683   gst_pad_set_caps (qtmux->srcpad, caps);
1684   gst_caps_unref (caps);
1685
1686   /* if not streaming, check if downstream is seekable */
1687   if (!qtmux->streamable) {
1688     gboolean seekable;
1689     GstQuery *query;
1690
1691     query = gst_query_new_seeking (GST_FORMAT_BYTES);
1692     if (gst_pad_peer_query (qtmux->srcpad, query)) {
1693       gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
1694       GST_INFO_OBJECT (qtmux, "downstream is %sseekable",
1695           seekable ? "" : "not ");
1696     } else {
1697       /* have to assume seeking is supported if query not handled downstream */
1698       GST_WARNING_OBJECT (qtmux, "downstream did not handle seeking query");
1699       seekable = FALSE;
1700     }
1701     gst_query_unref (query);
1702     if (!seekable) {
1703       if (qtmux_klass->format != GST_QT_MUX_FORMAT_ISML) {
1704         if (!qtmux->fast_start) {
1705           GST_ELEMENT_WARNING (qtmux, STREAM, FAILED,
1706               ("Downstream is not seekable and headers can't be rewritten"),
1707               (NULL));
1708           /* FIXME: Is there something better we can do? */
1709           qtmux->streamable = TRUE;
1710         }
1711       } else {
1712         GST_WARNING_OBJECT (qtmux, "downstream is not seekable, but "
1713             "streamable=false. Will ignore that and create streamable output "
1714             "instead");
1715         qtmux->streamable = TRUE;
1716         g_object_notify (G_OBJECT (qtmux), "streamable");
1717       }
1718     }
1719   }
1720
1721   /* let downstream know we think in BYTES and expect to do seeking later on */
1722   gst_segment_init (&segment, GST_FORMAT_BYTES);
1723   gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
1724
1725   /* initialize our moov recovery file */
1726   GST_OBJECT_LOCK (qtmux);
1727   if (qtmux->moov_recov_file_path) {
1728     GST_DEBUG_OBJECT (qtmux, "Openning moov recovery file: %s",
1729         qtmux->moov_recov_file_path);
1730     qtmux->moov_recov_file = g_fopen (qtmux->moov_recov_file_path, "wb+");
1731     if (qtmux->moov_recov_file == NULL) {
1732       GST_WARNING_OBJECT (qtmux, "Failed to open moov recovery file in %s",
1733           qtmux->moov_recov_file_path);
1734     } else {
1735       GSList *walk;
1736       gboolean fail = FALSE;
1737       AtomFTYP *ftyp = NULL;
1738       GstBuffer *prefix = NULL;
1739
1740       gst_qt_mux_prepare_ftyp (qtmux, &ftyp, &prefix);
1741
1742       if (!atoms_recov_write_headers (qtmux->moov_recov_file, ftyp, prefix,
1743               qtmux->moov, qtmux->timescale,
1744               g_slist_length (qtmux->sinkpads))) {
1745         GST_WARNING_OBJECT (qtmux, "Failed to write moov recovery file "
1746             "headers");
1747         fail = TRUE;
1748       }
1749
1750       atom_ftyp_free (ftyp);
1751       if (prefix)
1752         gst_buffer_unref (prefix);
1753
1754       for (walk = qtmux->sinkpads; walk && !fail; walk = g_slist_next (walk)) {
1755         GstCollectData *cdata = (GstCollectData *) walk->data;
1756         GstQTPad *qpad = (GstQTPad *) cdata;
1757         /* write info for each stream */
1758         fail = atoms_recov_write_trak_info (qtmux->moov_recov_file, qpad->trak);
1759         if (fail) {
1760           GST_WARNING_OBJECT (qtmux, "Failed to write trak info to recovery "
1761               "file");
1762         }
1763       }
1764       if (fail) {
1765         /* cleanup */
1766         fclose (qtmux->moov_recov_file);
1767         qtmux->moov_recov_file = NULL;
1768         GST_WARNING_OBJECT (qtmux, "An error was detected while writing to "
1769             "recover file, moov recovery won't work");
1770       }
1771     }
1772   }
1773   GST_OBJECT_UNLOCK (qtmux);
1774
1775   /* 
1776    * send mdat header if already needed, and mark position for later update.
1777    * We don't send ftyp now if we are on fast start mode, because we can
1778    * better fine tune using the information we gather to create the whole moov
1779    * atom.
1780    */
1781   if (qtmux->fast_start) {
1782     GST_OBJECT_LOCK (qtmux);
1783     qtmux->fast_start_file = g_fopen (qtmux->fast_start_file_path, "wb+");
1784     if (!qtmux->fast_start_file)
1785       goto open_failed;
1786     GST_OBJECT_UNLOCK (qtmux);
1787
1788     /* send a dummy buffer for preroll */
1789     ret = gst_qt_mux_send_buffer (qtmux, gst_buffer_new (), NULL, FALSE);
1790     if (ret != GST_FLOW_OK)
1791       goto exit;
1792
1793   } else {
1794     ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
1795     if (ret != GST_FLOW_OK) {
1796       goto exit;
1797     }
1798
1799     /* well, it's moov pos if fragmented ... */
1800     qtmux->mdat_pos = qtmux->header_size;
1801
1802     if (qtmux->fragment_duration) {
1803       GST_DEBUG_OBJECT (qtmux, "fragment duration %d ms, writing headers",
1804           qtmux->fragment_duration);
1805       /* also used as snapshot marker to indicate fragmented file */
1806       qtmux->fragment_sequence = 1;
1807       /* prepare moov and/or tags */
1808       gst_qt_mux_configure_moov (qtmux, NULL);
1809       gst_qt_mux_setup_metadata (qtmux);
1810       ret = gst_qt_mux_send_moov (qtmux, &qtmux->header_size, FALSE);
1811       if (ret != GST_FLOW_OK)
1812         return ret;
1813       /* extra atoms */
1814       ret =
1815           gst_qt_mux_send_extra_atoms (qtmux, TRUE, &qtmux->header_size, FALSE);
1816       if (ret != GST_FLOW_OK)
1817         return ret;
1818       /* prepare index */
1819       if (!qtmux->streamable)
1820         qtmux->mfra = atom_mfra_new (qtmux->context);
1821     } else {
1822       /* extended to ensure some spare space */
1823       ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE);
1824     }
1825   }
1826
1827 exit:
1828   return ret;
1829
1830   /* ERRORS */
1831 open_failed:
1832   {
1833     GST_ELEMENT_ERROR (qtmux, RESOURCE, OPEN_READ_WRITE,
1834         (("Could not open temporary file \"%s\""), qtmux->fast_start_file_path),
1835         GST_ERROR_SYSTEM);
1836     GST_OBJECT_UNLOCK (qtmux);
1837     return GST_FLOW_ERROR;
1838   }
1839 }
1840
1841 static GstFlowReturn
1842 gst_qt_mux_stop_file (GstQTMux * qtmux)
1843 {
1844   gboolean ret = GST_FLOW_OK;
1845   guint64 offset = 0, size = 0;
1846   GSList *walk;
1847   gboolean large_file;
1848   guint32 timescale;
1849   GstClockTime first_ts = GST_CLOCK_TIME_NONE;
1850
1851   /* for setting some subtitles fields */
1852   guint max_width = 0;
1853   guint max_height = 0;
1854
1855   GST_DEBUG_OBJECT (qtmux, "Updating remaining values and sending last data");
1856
1857   /* pushing last buffers for each pad */
1858   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
1859     GstCollectData *cdata = (GstCollectData *) walk->data;
1860     GstQTPad *qtpad = (GstQTPad *) cdata;
1861
1862     /* avoid add_buffer complaining if not negotiated
1863      * in which case no buffers either, so skipping */
1864     if (!qtpad->fourcc) {
1865       GST_DEBUG_OBJECT (qtmux, "Pad %s has never had buffers",
1866           GST_PAD_NAME (qtpad->collect.pad));
1867       continue;
1868     }
1869
1870     /* send last buffer; also flushes possibly queued buffers/ts */
1871     GST_DEBUG_OBJECT (qtmux, "Sending the last buffer for pad %s",
1872         GST_PAD_NAME (qtpad->collect.pad));
1873     ret = gst_qt_mux_add_buffer (qtmux, qtpad, NULL);
1874     if (ret != GST_FLOW_OK) {
1875       GST_WARNING_OBJECT (qtmux, "Failed to send last buffer for %s, "
1876           "flow return: %s", GST_PAD_NAME (qtpad->collect.pad),
1877           gst_flow_get_name (ret));
1878     }
1879
1880     /* having flushed above, can check for buffers now */
1881     if (!GST_CLOCK_TIME_IS_VALID (qtpad->first_ts)) {
1882       GST_DEBUG_OBJECT (qtmux, "Pad %s has no buffers",
1883           GST_PAD_NAME (qtpad->collect.pad));
1884       continue;
1885     }
1886
1887     /* determine max stream duration */
1888     if (!GST_CLOCK_TIME_IS_VALID (first_ts) ||
1889         (GST_CLOCK_TIME_IS_VALID (qtpad->first_ts) &&
1890             qtpad->last_dts > first_ts)) {
1891       first_ts = qtpad->last_dts;
1892     }
1893
1894     /* subtitles need to know the video width/height,
1895      * it is stored shifted 16 bits to the left according to the
1896      * spec */
1897     max_width = MAX (max_width, (qtpad->trak->tkhd.width >> 16));
1898     max_height = MAX (max_height, (qtpad->trak->tkhd.height >> 16));
1899
1900     /* update average bitrate of streams if needed */
1901     {
1902       guint32 avgbitrate = 0;
1903       guint32 maxbitrate = qtpad->max_bitrate;
1904
1905       if (qtpad->avg_bitrate)
1906         avgbitrate = qtpad->avg_bitrate;
1907       else if (qtpad->total_duration > 0)
1908         avgbitrate = (guint32) gst_util_uint64_scale_round (qtpad->total_bytes,
1909             8 * GST_SECOND, qtpad->total_duration);
1910
1911       atom_trak_update_bitrates (qtpad->trak, avgbitrate, maxbitrate);
1912     }
1913   }
1914
1915   /* need to update values on subtitle traks now that we know the
1916    * max width and height */
1917   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
1918     GstCollectData *cdata = (GstCollectData *) walk->data;
1919     GstQTPad *qtpad = (GstQTPad *) cdata;
1920
1921     if (!qtpad->fourcc) {
1922       GST_DEBUG_OBJECT (qtmux, "Pad %s has never had buffers",
1923           GST_PAD_NAME (qtpad->collect.pad));
1924       continue;
1925     }
1926
1927     if (qtpad->fourcc == FOURCC_tx3g) {
1928       atom_trak_tx3g_update_dimension (qtpad->trak, max_width, max_height);
1929     }
1930   }
1931
1932   if (qtmux->fragment_sequence) {
1933     GstSegment segment;
1934
1935     if (qtmux->mfra) {
1936       guint8 *data = NULL;
1937       GstBuffer *buf;
1938
1939       size = offset = 0;
1940       GST_DEBUG_OBJECT (qtmux, "adding mfra");
1941       if (!atom_mfra_copy_data (qtmux->mfra, &data, &size, &offset))
1942         goto serialize_error;
1943       buf = _gst_buffer_new_take_data (data, offset);
1944       ret = gst_qt_mux_send_buffer (qtmux, buf, NULL, FALSE);
1945       if (ret != GST_FLOW_OK)
1946         return ret;
1947     } else {
1948       /* must have been streamable; no need to write duration */
1949       GST_DEBUG_OBJECT (qtmux, "streamable file; nothing to stop");
1950       return GST_FLOW_OK;
1951     }
1952
1953     timescale = qtmux->timescale;
1954     /* only mvex duration is updated,
1955      * mvhd should be consistent with empty moov
1956      * (but TODO maybe some clients do not handle that well ?) */
1957     qtmux->moov->mvex.mehd.fragment_duration =
1958         gst_util_uint64_scale (first_ts, timescale, GST_SECOND);
1959     GST_DEBUG_OBJECT (qtmux, "rewriting moov with mvex duration %"
1960         GST_TIME_FORMAT, GST_TIME_ARGS (first_ts));
1961     /* seek and rewrite the header */
1962     gst_segment_init (&segment, GST_FORMAT_BYTES);
1963     segment.start = qtmux->mdat_pos;
1964     gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
1965     /* no need to seek back */
1966     return gst_qt_mux_send_moov (qtmux, NULL, FALSE);
1967   }
1968
1969   gst_qt_mux_configure_moov (qtmux, &timescale);
1970
1971   /* check for late streams */
1972   first_ts = GST_CLOCK_TIME_NONE;
1973   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
1974     GstCollectData *cdata = (GstCollectData *) walk->data;
1975     GstQTPad *qtpad = (GstQTPad *) cdata;
1976
1977     if (!GST_CLOCK_TIME_IS_VALID (first_ts) ||
1978         (GST_CLOCK_TIME_IS_VALID (qtpad->first_ts) &&
1979             qtpad->first_ts < first_ts)) {
1980       first_ts = qtpad->first_ts;
1981     }
1982   }
1983   GST_DEBUG_OBJECT (qtmux, "Media first ts selected: %" GST_TIME_FORMAT,
1984       GST_TIME_ARGS (first_ts));
1985   /* add EDTSs for late streams */
1986   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
1987     GstCollectData *cdata = (GstCollectData *) walk->data;
1988     GstQTPad *qtpad = (GstQTPad *) cdata;
1989     guint32 lateness;
1990     guint32 duration;
1991
1992     if (GST_CLOCK_TIME_IS_VALID (qtpad->first_ts) && qtpad->first_ts > first_ts) {
1993       GST_DEBUG_OBJECT (qtmux, "Pad %s is a late stream by %" GST_TIME_FORMAT,
1994           GST_PAD_NAME (qtpad->collect.pad),
1995           GST_TIME_ARGS (qtpad->first_ts - first_ts));
1996       lateness = gst_util_uint64_scale_round (qtpad->first_ts - first_ts,
1997           timescale, GST_SECOND);
1998       duration = qtpad->trak->tkhd.duration;
1999       atom_trak_add_elst_entry (qtpad->trak, lateness, (guint32) - 1,
2000           (guint32) (1 * 65536.0));
2001       atom_trak_add_elst_entry (qtpad->trak, duration, 0,
2002           (guint32) (1 * 65536.0));
2003
2004       /* need to add the empty time to the trak duration */
2005       duration += lateness;
2006
2007       qtpad->trak->tkhd.duration = duration;
2008
2009       /* And possibly grow the moov duration */
2010       if (duration > qtmux->moov->mvhd.time_info.duration) {
2011         qtmux->moov->mvhd.time_info.duration = duration;
2012         qtmux->moov->mvex.mehd.fragment_duration = duration;
2013       }
2014     }
2015   }
2016
2017   /* tags into file metadata */
2018   gst_qt_mux_setup_metadata (qtmux);
2019
2020   large_file = (qtmux->mdat_size > MDAT_LARGE_FILE_LIMIT);
2021   /* if faststart, update the offset of the atoms in the movie with the offset
2022    * that the movie headers before mdat will cause.
2023    * Also, send the ftyp */
2024   if (qtmux->fast_start_file) {
2025     GstFlowReturn flow_ret;
2026     offset = size = 0;
2027
2028     flow_ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
2029     if (flow_ret != GST_FLOW_OK) {
2030       goto ftyp_error;
2031     }
2032     /* copy into NULL to obtain size */
2033     if (!atom_moov_copy_data (qtmux->moov, NULL, &size, &offset))
2034       goto serialize_error;
2035     GST_DEBUG_OBJECT (qtmux, "calculated moov atom size %" G_GUINT64_FORMAT,
2036         offset);
2037     offset += qtmux->header_size + (large_file ? 16 : 8);
2038
2039     /* sum up with the extra atoms size */
2040     ret = gst_qt_mux_send_extra_atoms (qtmux, FALSE, &offset, FALSE);
2041     if (ret != GST_FLOW_OK)
2042       return ret;
2043   } else {
2044     offset = qtmux->header_size;
2045   }
2046   atom_moov_chunks_add_offset (qtmux->moov, offset);
2047
2048   /* moov */
2049   /* note: as of this point, we no longer care about tracking written data size,
2050    * since there is no more use for it anyway */
2051   ret = gst_qt_mux_send_moov (qtmux, NULL, FALSE);
2052   if (ret != GST_FLOW_OK)
2053     return ret;
2054
2055   /* extra atoms */
2056   ret = gst_qt_mux_send_extra_atoms (qtmux, TRUE, NULL, FALSE);
2057   if (ret != GST_FLOW_OK)
2058     return ret;
2059
2060   /* if needed, send mdat atom and move buffered data into it */
2061   if (qtmux->fast_start_file) {
2062     /* mdat_size = accumulated (buffered data) */
2063     ret = gst_qt_mux_send_mdat_header (qtmux, NULL, qtmux->mdat_size,
2064         large_file);
2065     if (ret != GST_FLOW_OK)
2066       return ret;
2067     ret = gst_qt_mux_send_buffered_data (qtmux, NULL);
2068     if (ret != GST_FLOW_OK)
2069       return ret;
2070   } else if (!qtmux->streamable) {
2071     /* mdat needs update iff not using faststart */
2072     GST_DEBUG_OBJECT (qtmux, "updating mdat size");
2073     ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
2074         qtmux->mdat_size, NULL);
2075     /* note; no seeking back to the end of file is done,
2076      * since we no longer write anything anyway */
2077   }
2078
2079   return ret;
2080
2081   /* ERRORS */
2082 serialize_error:
2083   {
2084     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
2085         ("Failed to serialize moov"));
2086     return GST_FLOW_ERROR;
2087   }
2088 ftyp_error:
2089   {
2090     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), ("Failed to send ftyp"));
2091     return GST_FLOW_ERROR;
2092   }
2093 }
2094
2095 static GstFlowReturn
2096 gst_qt_mux_pad_fragment_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
2097     GstBuffer * buf, gboolean force, guint32 nsamples, gint64 dts,
2098     guint32 delta, guint32 size, gboolean sync, gint64 pts_offset)
2099 {
2100   GstFlowReturn ret = GST_FLOW_OK;
2101
2102   /* setup if needed */
2103   if (G_UNLIKELY (!pad->traf || force))
2104     goto init;
2105
2106 flush:
2107   /* flush pad fragment if threshold reached,
2108    * or at new keyframe if we should be minding those in the first place */
2109   if (G_UNLIKELY (force || (sync && pad->sync) ||
2110           pad->fragment_duration < (gint64) delta)) {
2111     AtomMOOF *moof;
2112     guint64 size = 0, offset = 0;
2113     guint8 *data = NULL;
2114     GstBuffer *buffer;
2115     guint i, total_size;
2116
2117     /* now we know where moof ends up, update offset in tfra */
2118     if (pad->tfra)
2119       atom_tfra_update_offset (pad->tfra, qtmux->header_size);
2120
2121     moof = atom_moof_new (qtmux->context, qtmux->fragment_sequence);
2122     /* takes ownership */
2123     atom_moof_add_traf (moof, pad->traf);
2124     pad->traf = NULL;
2125     atom_moof_copy_data (moof, &data, &size, &offset);
2126     buffer = _gst_buffer_new_take_data (data, offset);
2127     GST_LOG_OBJECT (qtmux, "writing moof size %" G_GSIZE_FORMAT,
2128         gst_buffer_get_size (buffer));
2129     ret = gst_qt_mux_send_buffer (qtmux, buffer, &qtmux->header_size, FALSE);
2130
2131     /* and actual data */
2132     total_size = 0;
2133     for (i = 0; i < atom_array_get_len (&pad->fragment_buffers); i++) {
2134       total_size +=
2135           gst_buffer_get_size (atom_array_index (&pad->fragment_buffers, i));
2136     }
2137
2138     GST_LOG_OBJECT (qtmux, "writing %d buffers, total_size %d",
2139         atom_array_get_len (&pad->fragment_buffers), total_size);
2140     if (ret == GST_FLOW_OK)
2141       ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, total_size,
2142           FALSE);
2143     for (i = 0; i < atom_array_get_len (&pad->fragment_buffers); i++) {
2144       if (G_LIKELY (ret == GST_FLOW_OK))
2145         ret = gst_qt_mux_send_buffer (qtmux,
2146             atom_array_index (&pad->fragment_buffers, i), &qtmux->header_size,
2147             FALSE);
2148       else
2149         gst_buffer_unref (atom_array_index (&pad->fragment_buffers, i));
2150     }
2151
2152     atom_array_clear (&pad->fragment_buffers);
2153     atom_moof_free (moof);
2154     qtmux->fragment_sequence++;
2155     force = FALSE;
2156   }
2157
2158 init:
2159   if (G_UNLIKELY (!pad->traf)) {
2160     GST_LOG_OBJECT (qtmux, "setting up new fragment");
2161     pad->traf = atom_traf_new (qtmux->context, atom_trak_get_id (pad->trak));
2162     atom_array_init (&pad->fragment_buffers, 512);
2163     pad->fragment_duration = gst_util_uint64_scale (qtmux->fragment_duration,
2164         atom_trak_get_timescale (pad->trak), 1000);
2165
2166     if (G_UNLIKELY (qtmux->mfra && !pad->tfra)) {
2167       pad->tfra = atom_tfra_new (qtmux->context, atom_trak_get_id (pad->trak));
2168       atom_mfra_add_tfra (qtmux->mfra, pad->tfra);
2169     }
2170   }
2171
2172   /* add buffer and metadata */
2173   atom_traf_add_samples (pad->traf, delta, size, sync, pts_offset,
2174       pad->sync && sync);
2175   atom_array_append (&pad->fragment_buffers, buf, 256);
2176   pad->fragment_duration -= delta;
2177
2178   if (pad->tfra) {
2179     guint32 sn = atom_traf_get_sample_num (pad->traf);
2180
2181     if ((sync && pad->sync) || (sn == 1 && !pad->sync))
2182       atom_tfra_add_entry (pad->tfra, dts, sn);
2183   }
2184
2185   if (G_UNLIKELY (force))
2186     goto flush;
2187
2188   return ret;
2189 }
2190
2191 static void
2192 check_and_subtract_ts (GstQTMux * qtmux, GstClockTime * ts_a, GstClockTime ts_b)
2193 {
2194   if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (*ts_a))) {
2195     if (G_LIKELY (*ts_a >= ts_b)) {
2196       *ts_a -= ts_b;
2197     } else {
2198       *ts_a = 0;
2199       GST_WARNING_OBJECT (qtmux, "Subtraction would result in negative value, "
2200           "using 0 as result");
2201     }
2202   }
2203 }
2204
2205
2206 static GstFlowReturn
2207 gst_qt_mux_register_and_push_sample (GstQTMux * qtmux, GstQTPad * pad,
2208     GstBuffer * buffer, gboolean is_last_buffer, guint nsamples,
2209     gint64 last_dts, gint64 scaled_duration, guint sample_size,
2210     guint chunk_offset, gboolean sync, gboolean do_pts, gint64 pts_offset)
2211 {
2212   GstFlowReturn ret = GST_FLOW_OK;
2213
2214   /* note that a new chunk is started each time (not fancy but works) */
2215   if (qtmux->moov_recov_file) {
2216     if (!atoms_recov_write_trak_samples (qtmux->moov_recov_file, pad->trak,
2217             nsamples, (gint32) scaled_duration, sample_size, chunk_offset, sync,
2218             do_pts, pts_offset)) {
2219       GST_WARNING_OBJECT (qtmux, "Failed to write sample information to "
2220           "recovery file, disabling recovery");
2221       fclose (qtmux->moov_recov_file);
2222       qtmux->moov_recov_file = NULL;
2223     }
2224   }
2225
2226   if (qtmux->fragment_sequence) {
2227     /* ensure that always sync samples are marked as such */
2228     ret = gst_qt_mux_pad_fragment_add_buffer (qtmux, pad, buffer,
2229         is_last_buffer, nsamples, last_dts, (gint32) scaled_duration,
2230         sample_size, !pad->sync || sync, pts_offset);
2231   } else {
2232     atom_trak_add_samples (pad->trak, nsamples, (gint32) scaled_duration,
2233         sample_size, chunk_offset, sync, pts_offset);
2234     ret = gst_qt_mux_send_buffer (qtmux, buffer, &qtmux->mdat_size, TRUE);
2235   }
2236
2237   return ret;
2238 }
2239
2240 /*
2241  * Here we push the buffer and update the tables in the track atoms
2242  */
2243 static GstFlowReturn
2244 gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
2245 {
2246   GstBuffer *last_buf = NULL;
2247   GstClockTime duration;
2248   guint nsamples, sample_size;
2249   guint64 chunk_offset;
2250   gint64 last_dts, scaled_duration;
2251   gint64 pts_offset = 0;
2252   gboolean sync = FALSE, do_pts = FALSE;
2253   GstFlowReturn ret = GST_FLOW_OK;
2254
2255   if (!pad->fourcc)
2256     goto not_negotiated;
2257
2258   /* if this pad has a prepare function, call it */
2259   if (pad->prepare_buf_func != NULL) {
2260     buf = pad->prepare_buf_func (pad, buf, qtmux);
2261   }
2262
2263   if (G_LIKELY (buf != NULL && GST_CLOCK_TIME_IS_VALID (pad->first_ts) &&
2264           pad->first_ts != 0)) {
2265     buf = gst_buffer_make_writable (buf);
2266     check_and_subtract_ts (qtmux, &GST_BUFFER_DTS (buf), pad->first_ts);
2267     check_and_subtract_ts (qtmux, &GST_BUFFER_PTS (buf), pad->first_ts);
2268   }
2269
2270   last_buf = pad->last_buf;
2271
2272   /* if buffer has missing DTS, we take either segment start or previous buffer end time, 
2273      which ever is later */
2274   if (buf && !GST_BUFFER_DTS_IS_VALID (buf)) {
2275     GstClockTime last_buf_duration = last_buf
2276         && GST_BUFFER_DURATION_IS_VALID (last_buf) ?
2277         GST_BUFFER_DURATION (last_buf) : 0;
2278
2279     buf = gst_buffer_make_writable (buf);
2280     GST_BUFFER_DTS (buf) =
2281         gst_segment_to_running_time (&pad->collect.segment, GST_FORMAT_TIME,
2282         pad->collect.segment.start);
2283     if (GST_CLOCK_TIME_IS_VALID (pad->first_ts))
2284       check_and_subtract_ts (qtmux, &GST_BUFFER_DTS (buf), pad->first_ts);
2285
2286     if (last_buf
2287         && (GST_BUFFER_DTS (last_buf) + last_buf_duration) >
2288         GST_BUFFER_DTS (buf)) {
2289       GST_BUFFER_DTS (buf) = GST_BUFFER_DTS (last_buf) + last_buf_duration;
2290     }
2291
2292     if (GST_BUFFER_PTS_IS_VALID (buf))
2293       GST_BUFFER_DTS (buf) = MIN (GST_BUFFER_DTS (buf), GST_BUFFER_PTS (buf));
2294   }
2295
2296   if (last_buf && !buf && !GST_BUFFER_DURATION_IS_VALID (last_buf)) {
2297     /* this is last buffer; there is no next buffer so we need valid number as duration */
2298     last_buf = gst_buffer_make_writable (last_buf);
2299     GST_BUFFER_DURATION (last_buf) = 0;
2300   }
2301
2302   if (last_buf == NULL) {
2303 #ifndef GST_DISABLE_GST_DEBUG
2304     if (buf == NULL) {
2305       GST_DEBUG_OBJECT (qtmux, "Pad %s has no previous buffer stored and "
2306           "received NULL buffer, doing nothing",
2307           GST_PAD_NAME (pad->collect.pad));
2308     } else {
2309       GST_LOG_OBJECT (qtmux,
2310           "Pad %s has no previous buffer stored, storing now",
2311           GST_PAD_NAME (pad->collect.pad));
2312     }
2313 #endif
2314     pad->last_buf = buf;
2315     goto exit;
2316   } else
2317     gst_buffer_ref (last_buf);
2318
2319   /* if this is the first buffer, store the timestamp */
2320   if (G_UNLIKELY (pad->first_ts == GST_CLOCK_TIME_NONE) && last_buf) {
2321     if (GST_BUFFER_DTS_IS_VALID (last_buf)) {
2322       /* first pad always has DTS. If it was not provided by upstream it was set to segment start */
2323       pad->first_ts = GST_BUFFER_DTS (last_buf);
2324     } else if (GST_BUFFER_PTS_IS_VALID (last_buf)) {
2325       pad->first_ts = GST_BUFFER_PTS (last_buf);
2326     }
2327
2328     if (GST_CLOCK_TIME_IS_VALID (pad->first_ts)) {
2329       GST_DEBUG ("setting first_ts to %" G_GUINT64_FORMAT, pad->first_ts);
2330       last_buf = gst_buffer_make_writable (last_buf);
2331       check_and_subtract_ts (qtmux, &GST_BUFFER_DTS (last_buf), pad->first_ts);
2332       check_and_subtract_ts (qtmux, &GST_BUFFER_PTS (last_buf), pad->first_ts);
2333       if (buf) {
2334         buf = gst_buffer_make_writable (buf);
2335         check_and_subtract_ts (qtmux, &GST_BUFFER_DTS (buf), pad->first_ts);
2336         check_and_subtract_ts (qtmux, &GST_BUFFER_PTS (buf), pad->first_ts);
2337       }
2338     } else {
2339       GST_ERROR_OBJECT (qtmux, "First buffer for pad %s has no timestamp, "
2340           "using 0 as first timestamp", GST_PAD_NAME (pad->collect.pad));
2341       pad->first_ts = 0;
2342     }
2343     GST_DEBUG_OBJECT (qtmux, "Stored first timestamp for pad %s %"
2344         GST_TIME_FORMAT, GST_PAD_NAME (pad->collect.pad),
2345         GST_TIME_ARGS (pad->first_ts));
2346
2347   }
2348
2349   if (last_buf && buf && GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (buf)) &&
2350       GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (last_buf)) &&
2351       GST_BUFFER_DTS (buf) < GST_BUFFER_DTS (last_buf)) {
2352     GST_ERROR ("decreasing DTS value %" GST_TIME_FORMAT " < %" GST_TIME_FORMAT,
2353         GST_TIME_ARGS (GST_BUFFER_DTS (buf)),
2354         GST_TIME_ARGS (GST_BUFFER_DTS (last_buf)));
2355     GST_BUFFER_DTS (buf) = GST_BUFFER_DTS (last_buf);
2356   }
2357
2358   /* duration actually means time delta between samples, so we calculate
2359    * the duration based on the difference in DTS or PTS, falling back
2360    * to DURATION if the other two don't exist, such as with the last
2361    * sample before EOS. */
2362   duration = GST_BUFFER_DURATION (last_buf);
2363   if (!pad->sparse) {
2364     if (last_buf && buf && GST_BUFFER_DTS_IS_VALID (buf)
2365         && GST_BUFFER_DTS_IS_VALID (last_buf))
2366       duration = GST_BUFFER_DTS (buf) - GST_BUFFER_DTS (last_buf);
2367     else if (last_buf && buf && GST_BUFFER_PTS_IS_VALID (buf)
2368         && GST_BUFFER_PTS_IS_VALID (last_buf))
2369       duration = GST_BUFFER_PTS (buf) - GST_BUFFER_PTS (last_buf);
2370   }
2371
2372   gst_buffer_replace (&pad->last_buf, buf);
2373
2374   /* for computing the avg bitrate */
2375   if (G_LIKELY (last_buf)) {
2376     pad->total_bytes += gst_buffer_get_size (last_buf);
2377     pad->total_duration += duration;
2378   }
2379
2380   last_dts = gst_util_uint64_scale_round (pad->last_dts,
2381       atom_trak_get_timescale (pad->trak), GST_SECOND);
2382
2383   /* fragments only deal with 1 buffer == 1 chunk (== 1 sample) */
2384   if (pad->sample_size && !qtmux->fragment_sequence) {
2385     /* Constant size packets: usually raw audio (with many samples per
2386        buffer (= chunk)), but can also be fixed-packet-size codecs like ADPCM
2387      */
2388     sample_size = pad->sample_size;
2389     if (gst_buffer_get_size (last_buf) % sample_size != 0)
2390       goto fragmented_sample;
2391     /* note: qt raw audio storage warps it implicitly into a timewise
2392      * perfect stream, discarding buffer times */
2393     if (GST_BUFFER_DURATION (last_buf) != GST_CLOCK_TIME_NONE) {
2394       nsamples = gst_util_uint64_scale_round (GST_BUFFER_DURATION (last_buf),
2395           atom_trak_get_timescale (pad->trak), GST_SECOND);
2396     } else {
2397       nsamples = gst_buffer_get_size (last_buf) / sample_size;
2398     }
2399     if (nsamples > 0)
2400       duration = GST_BUFFER_DURATION (last_buf) / nsamples;
2401     else
2402       duration = 0;
2403
2404     /* timescale = samplerate */
2405     scaled_duration = 1;
2406     pad->last_dts += duration * nsamples;
2407   } else {
2408     nsamples = 1;
2409     sample_size = gst_buffer_get_size (last_buf);
2410     if ((pad->last_buf && GST_BUFFER_DTS_IS_VALID (pad->last_buf))
2411         || GST_BUFFER_DTS_IS_VALID (last_buf)) {
2412       gint64 scaled_dts;
2413       if (pad->last_buf && GST_BUFFER_DTS_IS_VALID (pad->last_buf)) {
2414         pad->last_dts = GST_BUFFER_DTS (pad->last_buf);
2415       } else {
2416         pad->last_dts = GST_BUFFER_DTS (last_buf) +
2417             GST_BUFFER_DURATION (last_buf);
2418       }
2419       if ((gint64) (pad->last_dts) < 0) {
2420         scaled_dts = -gst_util_uint64_scale_round (-pad->last_dts,
2421             atom_trak_get_timescale (pad->trak), GST_SECOND);
2422       } else {
2423         scaled_dts = gst_util_uint64_scale_round (pad->last_dts,
2424             atom_trak_get_timescale (pad->trak), GST_SECOND);
2425       }
2426       scaled_duration = scaled_dts - last_dts;
2427       last_dts = scaled_dts;
2428     } else {
2429       /* first convert intended timestamp (in GstClockTime resolution) to
2430        * trak timescale, then derive delta;
2431        * this ensures sums of (scale)delta add up to converted timestamp,
2432        * which only deviates at most 1/scale from timestamp itself */
2433       scaled_duration = gst_util_uint64_scale_round (pad->last_dts + duration,
2434           atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts;
2435       pad->last_dts += duration;
2436     }
2437   }
2438   chunk_offset = qtmux->mdat_size;
2439
2440   GST_LOG_OBJECT (qtmux,
2441       "Pad (%s) dts updated to %" GST_TIME_FORMAT,
2442       GST_PAD_NAME (pad->collect.pad), GST_TIME_ARGS (pad->last_dts));
2443   GST_LOG_OBJECT (qtmux,
2444       "Adding %d samples to track, duration: %" G_GUINT64_FORMAT
2445       " size: %" G_GUINT32_FORMAT " chunk offset: %" G_GUINT64_FORMAT,
2446       nsamples, scaled_duration, sample_size, chunk_offset);
2447
2448   /* might be a sync sample */
2449   if (pad->sync &&
2450       !GST_BUFFER_FLAG_IS_SET (last_buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
2451     GST_LOG_OBJECT (qtmux, "Adding new sync sample entry for track of pad %s",
2452         GST_PAD_NAME (pad->collect.pad));
2453     sync = TRUE;
2454   }
2455
2456   if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (last_buf))) {
2457     do_pts = TRUE;
2458     last_dts = gst_util_uint64_scale_round (GST_BUFFER_DTS (last_buf),
2459         atom_trak_get_timescale (pad->trak), GST_SECOND);
2460     pts_offset =
2461         (gint64) (gst_util_uint64_scale_round (GST_BUFFER_PTS (last_buf),
2462             atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts);
2463
2464   } else {
2465     pts_offset = 0;
2466     do_pts = TRUE;
2467     last_dts = gst_util_uint64_scale_round (GST_BUFFER_PTS (last_buf),
2468         atom_trak_get_timescale (pad->trak), GST_SECOND);
2469   }
2470   GST_DEBUG ("dts: %" GST_TIME_FORMAT " pts: %" GST_TIME_FORMAT
2471       " timebase_dts: %d pts_offset: %d",
2472       GST_TIME_ARGS (GST_BUFFER_DTS (last_buf)),
2473       GST_TIME_ARGS (GST_BUFFER_PTS (last_buf)),
2474       (int) (last_dts), (int) (pts_offset));
2475
2476   /*
2477    * Each buffer starts a new chunk, so we can assume the buffer
2478    * duration is the chunk duration
2479    */
2480   if (GST_CLOCK_TIME_IS_VALID (duration) && (duration > qtmux->longest_chunk ||
2481           !GST_CLOCK_TIME_IS_VALID (qtmux->longest_chunk))) {
2482     GST_DEBUG_OBJECT (qtmux, "New longest chunk found: %" GST_TIME_FORMAT
2483         ", pad %s", GST_TIME_ARGS (duration), GST_PAD_NAME (pad->collect.pad));
2484     qtmux->longest_chunk = duration;
2485   }
2486
2487   /* now we go and register this buffer/sample all over */
2488   ret = gst_qt_mux_register_and_push_sample (qtmux, pad, last_buf,
2489       buf == NULL, nsamples, last_dts, scaled_duration, sample_size,
2490       chunk_offset, sync, do_pts, pts_offset);
2491
2492   /* if this is sparse and we have a next buffer, check if there is any gap
2493    * between them to insert an empty sample */
2494   if (pad->sparse && buf) {
2495     if (pad->create_empty_buffer) {
2496       GstBuffer *empty_buf;
2497       gint64 empty_duration =
2498           GST_BUFFER_TIMESTAMP (buf) - (GST_BUFFER_TIMESTAMP (last_buf) +
2499           duration);
2500       gint64 empty_duration_scaled;
2501
2502       empty_buf = pad->create_empty_buffer (pad, empty_duration);
2503
2504       empty_duration_scaled = gst_util_uint64_scale_round (empty_duration,
2505           atom_trak_get_timescale (pad->trak), GST_SECOND);
2506
2507       pad->total_bytes += gst_buffer_get_size (empty_buf);
2508       pad->total_duration += duration;
2509
2510       ret =
2511           gst_qt_mux_register_and_push_sample (qtmux, pad, empty_buf, FALSE, 1,
2512           last_dts + scaled_duration, empty_duration_scaled,
2513           gst_buffer_get_size (empty_buf), qtmux->mdat_size, sync, do_pts, 0);
2514     } else {
2515       /* our only case currently is tx3g subtitles, so there is no reason to fill this yet */
2516       g_assert_not_reached ();
2517       GST_WARNING_OBJECT (qtmux,
2518           "no empty buffer creation function found for pad %s",
2519           GST_PAD_NAME (pad->collect.pad));
2520     }
2521   }
2522
2523   if (buf)
2524     gst_buffer_unref (buf);
2525
2526 exit:
2527
2528   return ret;
2529
2530   /* ERRORS */
2531 bail:
2532   {
2533     if (buf)
2534       gst_buffer_unref (buf);
2535     gst_buffer_unref (last_buf);
2536     return GST_FLOW_ERROR;
2537   }
2538 fragmented_sample:
2539   {
2540     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
2541         ("Audio buffer contains fragmented sample."));
2542     goto bail;
2543   }
2544 not_negotiated:
2545   {
2546     GST_ELEMENT_ERROR (qtmux, CORE, NEGOTIATION, (NULL),
2547         ("format wasn't negotiated before buffer flow on pad %s",
2548             GST_PAD_NAME (pad->collect.pad)));
2549     if (buf)
2550       gst_buffer_unref (buf);
2551     return GST_FLOW_NOT_NEGOTIATED;
2552   }
2553 }
2554
2555 static GstFlowReturn
2556 gst_qt_mux_handle_buffer (GstCollectPads * pads, GstCollectData * cdata,
2557     GstBuffer * buf, gpointer user_data)
2558 {
2559   GstFlowReturn ret = GST_FLOW_OK;
2560   GstQTMux *qtmux = GST_QT_MUX_CAST (user_data);
2561   GstQTPad *best_pad = NULL;
2562   GstClockTime best_time = GST_CLOCK_TIME_NONE;
2563
2564   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_STARTED)) {
2565     if ((ret = gst_qt_mux_start_file (qtmux)) != GST_FLOW_OK)
2566       return ret;
2567     else
2568       qtmux->state = GST_QT_MUX_STATE_DATA;
2569   }
2570
2571   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS))
2572     return GST_FLOW_EOS;
2573
2574   best_pad = (GstQTPad *) cdata;
2575
2576   /* clipping already converted to running time */
2577   if (best_pad != NULL) {
2578     g_assert (buf);
2579     best_time = GST_BUFFER_PTS (buf);
2580     GST_LOG_OBJECT (qtmux, "selected pad %s with time %" GST_TIME_FORMAT,
2581         GST_PAD_NAME (best_pad->collect.pad), GST_TIME_ARGS (best_time));
2582     ret = gst_qt_mux_add_buffer (qtmux, best_pad, buf);
2583   } else {
2584     ret = gst_qt_mux_stop_file (qtmux);
2585     if (ret == GST_FLOW_OK) {
2586       GST_DEBUG_OBJECT (qtmux, "Pushing eos");
2587       gst_pad_push_event (qtmux->srcpad, gst_event_new_eos ());
2588       ret = GST_FLOW_EOS;
2589     } else {
2590       GST_WARNING_OBJECT (qtmux, "Failed to stop file: %s",
2591           gst_flow_get_name (ret));
2592     }
2593     qtmux->state = GST_QT_MUX_STATE_EOS;
2594   }
2595
2596   return ret;
2597 }
2598
2599 static gboolean
2600 check_field (GQuark field_id, const GValue * value, gpointer user_data)
2601 {
2602   GstStructure *structure = (GstStructure *) user_data;
2603   const GValue *other = gst_structure_id_get_value (structure, field_id);
2604   if (other == NULL)
2605     return FALSE;
2606   return gst_value_compare (value, other) == GST_VALUE_EQUAL;
2607 }
2608
2609 static gboolean
2610 gst_qtmux_caps_is_subset_full (GstQTMux * qtmux, GstCaps * subset,
2611     GstCaps * superset)
2612 {
2613   GstStructure *sub_s = gst_caps_get_structure (subset, 0);
2614   GstStructure *sup_s = gst_caps_get_structure (superset, 0);
2615
2616   return gst_structure_foreach (sub_s, check_field, sup_s);
2617 }
2618
2619 static gboolean
2620 gst_qt_mux_audio_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
2621 {
2622   GstPad *pad = qtpad->collect.pad;
2623   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
2624   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
2625   GstStructure *structure;
2626   const gchar *mimetype;
2627   gint rate, channels;
2628   const GValue *value = NULL;
2629   const GstBuffer *codec_data = NULL;
2630   GstQTMuxFormat format;
2631   AudioSampleEntry entry = { 0, };
2632   AtomInfo *ext_atom = NULL;
2633   gint constant_size = 0;
2634   const gchar *stream_format;
2635
2636   qtpad->prepare_buf_func = NULL;
2637
2638   /* does not go well to renegotiate stream mid-way, unless
2639    * the old caps are a subset of the new one (this means upstream
2640    * added more info to the caps, as both should be 'fixed' caps) */
2641   if (qtpad->fourcc) {
2642     GstCaps *current_caps;
2643
2644     current_caps = gst_pad_get_current_caps (pad);
2645     g_assert (caps != NULL);
2646
2647     if (!gst_qtmux_caps_is_subset_full (qtmux, current_caps, caps)) {
2648       gst_caps_unref (current_caps);
2649       goto refuse_renegotiation;
2650     }
2651     GST_DEBUG_OBJECT (qtmux,
2652         "pad %s accepted renegotiation to %" GST_PTR_FORMAT " from %"
2653         GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, current_caps);
2654     gst_caps_unref (current_caps);
2655   }
2656
2657   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
2658       GST_DEBUG_PAD_NAME (pad), caps);
2659
2660   format = qtmux_klass->format;
2661   structure = gst_caps_get_structure (caps, 0);
2662   mimetype = gst_structure_get_name (structure);
2663
2664   /* common info */
2665   if (!gst_structure_get_int (structure, "channels", &channels) ||
2666       !gst_structure_get_int (structure, "rate", &rate)) {
2667     goto refuse_caps;
2668   }
2669
2670   /* optional */
2671   value = gst_structure_get_value (structure, "codec_data");
2672   if (value != NULL)
2673     codec_data = gst_value_get_buffer (value);
2674
2675   qtpad->is_out_of_order = FALSE;
2676
2677   /* set common properties */
2678   entry.sample_rate = rate;
2679   entry.channels = channels;
2680   /* default */
2681   entry.sample_size = 16;
2682   /* this is the typical compressed case */
2683   if (format == GST_QT_MUX_FORMAT_QT) {
2684     entry.version = 1;
2685     entry.compression_id = -2;
2686   }
2687
2688   /* now map onto a fourcc, and some extra properties */
2689   if (strcmp (mimetype, "audio/mpeg") == 0) {
2690     gint mpegversion = 0;
2691     gint layer = -1;
2692
2693     gst_structure_get_int (structure, "mpegversion", &mpegversion);
2694     switch (mpegversion) {
2695       case 1:
2696         gst_structure_get_int (structure, "layer", &layer);
2697         switch (layer) {
2698           case 3:
2699             /* mp3 */
2700             /* note: QuickTime player does not like mp3 either way in iso/mp4 */
2701             if (format == GST_QT_MUX_FORMAT_QT)
2702               entry.fourcc = FOURCC__mp3;
2703             else {
2704               entry.fourcc = FOURCC_mp4a;
2705               ext_atom =
2706                   build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG1_P3,
2707                   ESDS_STREAM_TYPE_AUDIO, codec_data, qtpad->avg_bitrate,
2708                   qtpad->max_bitrate);
2709             }
2710             entry.samples_per_packet = 1152;
2711             entry.bytes_per_sample = 2;
2712             break;
2713         }
2714         break;
2715       case 4:
2716
2717         /* check stream-format */
2718         stream_format = gst_structure_get_string (structure, "stream-format");
2719         if (stream_format) {
2720           if (strcmp (stream_format, "raw") != 0) {
2721             GST_WARNING_OBJECT (qtmux, "Unsupported AAC stream-format %s, "
2722                 "please use 'raw'", stream_format);
2723             goto refuse_caps;
2724           }
2725         } else {
2726           GST_WARNING_OBJECT (qtmux, "No stream-format present in caps, "
2727               "assuming 'raw'");
2728         }
2729
2730         if (!codec_data || gst_buffer_get_size ((GstBuffer *) codec_data) < 2)
2731           GST_WARNING_OBJECT (qtmux, "no (valid) codec_data for AAC audio");
2732         else {
2733           guint8 profile;
2734
2735           gst_buffer_extract ((GstBuffer *) codec_data, 0, &profile, 1);
2736           /* warn if not Low Complexity profile */
2737           profile >>= 3;
2738           if (profile != 2)
2739             GST_WARNING_OBJECT (qtmux,
2740                 "non-LC AAC may not run well on (Apple) QuickTime/iTunes");
2741         }
2742
2743         /* AAC */
2744         entry.fourcc = FOURCC_mp4a;
2745
2746         if (format == GST_QT_MUX_FORMAT_QT)
2747           ext_atom = build_mov_aac_extension (qtpad->trak, codec_data,
2748               qtpad->avg_bitrate, qtpad->max_bitrate);
2749         else
2750           ext_atom =
2751               build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P3,
2752               ESDS_STREAM_TYPE_AUDIO, codec_data, qtpad->avg_bitrate,
2753               qtpad->max_bitrate);
2754         break;
2755       default:
2756         break;
2757     }
2758   } else if (strcmp (mimetype, "audio/AMR") == 0) {
2759     entry.fourcc = FOURCC_samr;
2760     entry.sample_size = 16;
2761     entry.samples_per_packet = 160;
2762     entry.bytes_per_sample = 2;
2763     ext_atom = build_amr_extension ();
2764   } else if (strcmp (mimetype, "audio/AMR-WB") == 0) {
2765     entry.fourcc = FOURCC_sawb;
2766     entry.sample_size = 16;
2767     entry.samples_per_packet = 320;
2768     entry.bytes_per_sample = 2;
2769     ext_atom = build_amr_extension ();
2770   } else if (strcmp (mimetype, "audio/x-raw") == 0) {
2771     GstAudioInfo info;
2772
2773     gst_audio_info_init (&info);
2774     if (!gst_audio_info_from_caps (&info, caps))
2775       goto refuse_caps;
2776
2777     /* spec has no place for a distinction in these */
2778     if (info.finfo->width != info.finfo->depth) {
2779       GST_DEBUG_OBJECT (qtmux, "width must be same as depth!");
2780       goto refuse_caps;
2781     }
2782
2783     if ((info.finfo->flags & GST_AUDIO_FORMAT_FLAG_SIGNED)) {
2784       if (info.finfo->endianness == G_LITTLE_ENDIAN)
2785         entry.fourcc = FOURCC_sowt;
2786       else if (info.finfo->endianness == G_BIG_ENDIAN)
2787         entry.fourcc = FOURCC_twos;
2788       /* maximum backward compatibility; only new version for > 16 bit */
2789       if (info.finfo->depth <= 16)
2790         entry.version = 0;
2791       /* not compressed in any case */
2792       entry.compression_id = 0;
2793       /* QT spec says: max at 16 bit even if sample size were actually larger,
2794        * however, most players (e.g. QuickTime!) seem to disagree, so ... */
2795       entry.sample_size = info.finfo->depth;
2796       entry.bytes_per_sample = info.finfo->depth / 8;
2797       entry.samples_per_packet = 1;
2798       entry.bytes_per_packet = info.finfo->depth / 8;
2799       entry.bytes_per_frame = entry.bytes_per_packet * info.channels;
2800     } else {
2801       if (info.finfo->width == 8 && info.finfo->depth == 8) {
2802         /* fall back to old 8-bit version */
2803         entry.fourcc = FOURCC_raw_;
2804         entry.version = 0;
2805         entry.compression_id = 0;
2806         entry.sample_size = 8;
2807       } else {
2808         GST_DEBUG_OBJECT (qtmux, "non 8-bit PCM must be signed");
2809         goto refuse_caps;
2810       }
2811     }
2812     constant_size = (info.finfo->depth / 8) * info.channels;
2813   } else if (strcmp (mimetype, "audio/x-alaw") == 0) {
2814     entry.fourcc = FOURCC_alaw;
2815     entry.samples_per_packet = 1023;
2816     entry.bytes_per_sample = 2;
2817   } else if (strcmp (mimetype, "audio/x-mulaw") == 0) {
2818     entry.fourcc = FOURCC_ulaw;
2819     entry.samples_per_packet = 1023;
2820     entry.bytes_per_sample = 2;
2821   } else if (strcmp (mimetype, "audio/x-adpcm") == 0) {
2822     gint blocksize;
2823     if (!gst_structure_get_int (structure, "block_align", &blocksize)) {
2824       GST_DEBUG_OBJECT (qtmux, "broken caps, block_align missing");
2825       goto refuse_caps;
2826     }
2827     /* Currently only supports WAV-style IMA ADPCM, for which the codec id is
2828        0x11 */
2829     entry.fourcc = MS_WAVE_FOURCC (0x11);
2830     /* 4 byte header per channel (including one sample). 2 samples per byte
2831        remaining. Simplifying gives the following (samples per block per
2832        channel) */
2833     entry.samples_per_packet = 2 * blocksize / channels - 7;
2834     entry.bytes_per_sample = 2;
2835
2836     entry.bytes_per_frame = blocksize;
2837     entry.bytes_per_packet = blocksize / channels;
2838     /* ADPCM has constant size packets */
2839     constant_size = 1;
2840     /* TODO: I don't really understand why this helps, but it does! Constant
2841      * size and compression_id of -2 seem to be incompatible, and other files
2842      * in the wild use this too. */
2843     entry.compression_id = -1;
2844
2845     ext_atom = build_ima_adpcm_extension (channels, rate, blocksize);
2846   } else if (strcmp (mimetype, "audio/x-alac") == 0) {
2847     GstBuffer *codec_config;
2848     gint len;
2849     GstMapInfo map;
2850
2851     entry.fourcc = FOURCC_alac;
2852     gst_buffer_map ((GstBuffer *) codec_data, &map, GST_MAP_READ);
2853     /* let's check if codec data already comes with 'alac' atom prefix */
2854     if (!codec_data || (len = map.size) < 28) {
2855       GST_DEBUG_OBJECT (qtmux, "broken caps, codec data missing");
2856       gst_buffer_unmap ((GstBuffer *) codec_data, &map);
2857       goto refuse_caps;
2858     }
2859     if (GST_READ_UINT32_LE (map.data + 4) == FOURCC_alac) {
2860       len -= 8;
2861       codec_config =
2862           gst_buffer_copy_region ((GstBuffer *) codec_data, 0, 8, len);
2863     } else {
2864       codec_config = gst_buffer_ref ((GstBuffer *) codec_data);
2865     }
2866     gst_buffer_unmap ((GstBuffer *) codec_data, &map);
2867     if (len != 28) {
2868       /* does not look good, but perhaps some trailing unneeded stuff */
2869       GST_WARNING_OBJECT (qtmux, "unexpected codec-data size, possibly broken");
2870     }
2871     if (format == GST_QT_MUX_FORMAT_QT)
2872       ext_atom = build_mov_alac_extension (qtpad->trak, codec_config);
2873     else
2874       ext_atom = build_codec_data_extension (FOURCC_alac, codec_config);
2875     /* set some more info */
2876     gst_buffer_map (codec_config, &map, GST_MAP_READ);
2877     entry.bytes_per_sample = 2;
2878     entry.samples_per_packet = GST_READ_UINT32_BE (map.data + 4);
2879     gst_buffer_unmap (codec_config, &map);
2880     gst_buffer_unref (codec_config);
2881   }
2882
2883   if (!entry.fourcc)
2884     goto refuse_caps;
2885
2886   /* ok, set the pad info accordingly */
2887   qtpad->fourcc = entry.fourcc;
2888   qtpad->sample_size = constant_size;
2889   atom_trak_set_audio_type (qtpad->trak, qtmux->context, &entry,
2890       qtmux->trak_timescale ? qtmux->trak_timescale : entry.sample_rate,
2891       ext_atom, constant_size);
2892
2893   gst_object_unref (qtmux);
2894   return TRUE;
2895
2896   /* ERRORS */
2897 refuse_caps:
2898   {
2899     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
2900         GST_PAD_NAME (pad), caps);
2901     gst_object_unref (qtmux);
2902     return FALSE;
2903   }
2904 refuse_renegotiation:
2905   {
2906     GST_WARNING_OBJECT (qtmux,
2907         "pad %s refused renegotiation to %" GST_PTR_FORMAT,
2908         GST_PAD_NAME (pad), caps);
2909     gst_object_unref (qtmux);
2910     return FALSE;
2911   }
2912 }
2913
2914 /* scale rate up or down by factor of 10 to fit into [1000,10000] interval */
2915 static guint32
2916 adjust_rate (guint64 rate)
2917 {
2918   if (rate == 0)
2919     return 10000;
2920
2921   while (rate >= 10000)
2922     rate /= 10;
2923
2924   while (rate < 1000)
2925     rate *= 10;
2926
2927   return (guint32) rate;
2928 }
2929
2930 static gboolean
2931 gst_qt_mux_video_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
2932 {
2933   GstPad *pad = qtpad->collect.pad;
2934   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
2935   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
2936   GstStructure *structure;
2937   const gchar *mimetype;
2938   gint width, height, depth = -1;
2939   gint framerate_num, framerate_den;
2940   guint32 rate;
2941   const GValue *value = NULL;
2942   const GstBuffer *codec_data = NULL;
2943   VisualSampleEntry entry = { 0, };
2944   GstQTMuxFormat format;
2945   AtomInfo *ext_atom = NULL;
2946   GList *ext_atom_list = NULL;
2947   gboolean sync = FALSE;
2948   int par_num, par_den;
2949
2950   qtpad->prepare_buf_func = NULL;
2951
2952   /* does not go well to renegotiate stream mid-way, unless
2953    * the old caps are a subset of the new one (this means upstream
2954    * added more info to the caps, as both should be 'fixed' caps) */
2955   if (qtpad->fourcc) {
2956     GstCaps *current_caps;
2957
2958     current_caps = gst_pad_get_current_caps (pad);
2959     g_assert (caps != NULL);
2960
2961     if (!gst_qtmux_caps_is_subset_full (qtmux, current_caps, caps)) {
2962       gst_caps_unref (current_caps);
2963       goto refuse_renegotiation;
2964     }
2965     GST_DEBUG_OBJECT (qtmux,
2966         "pad %s accepted renegotiation to %" GST_PTR_FORMAT " from %"
2967         GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, current_caps);
2968     gst_caps_unref (current_caps);
2969   }
2970
2971   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
2972       GST_DEBUG_PAD_NAME (pad), caps);
2973
2974   format = qtmux_klass->format;
2975   structure = gst_caps_get_structure (caps, 0);
2976   mimetype = gst_structure_get_name (structure);
2977
2978   /* required parts */
2979   if (!gst_structure_get_int (structure, "width", &width) ||
2980       !gst_structure_get_int (structure, "height", &height))
2981     goto refuse_caps;
2982
2983   /* optional */
2984   depth = -1;
2985   /* works as a default timebase */
2986   framerate_num = 10000;
2987   framerate_den = 1;
2988   gst_structure_get_fraction (structure, "framerate", &framerate_num,
2989       &framerate_den);
2990   gst_structure_get_int (structure, "depth", &depth);
2991   value = gst_structure_get_value (structure, "codec_data");
2992   if (value != NULL)
2993     codec_data = gst_value_get_buffer (value);
2994
2995   par_num = 1;
2996   par_den = 1;
2997   gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_num,
2998       &par_den);
2999
3000   qtpad->is_out_of_order = FALSE;
3001
3002   /* bring frame numerator into a range that ensures both reasonable resolution
3003    * as well as a fair duration */
3004   rate = qtmux->trak_timescale ?
3005       qtmux->trak_timescale : adjust_rate (framerate_num);
3006   GST_DEBUG_OBJECT (qtmux, "Rate of video track selected: %" G_GUINT32_FORMAT,
3007       rate);
3008
3009   /* set common properties */
3010   entry.width = width;
3011   entry.height = height;
3012   entry.par_n = par_num;
3013   entry.par_d = par_den;
3014   /* should be OK according to qt and iso spec, override if really needed */
3015   entry.color_table_id = -1;
3016   entry.frame_count = 1;
3017   entry.depth = 24;
3018
3019   /* sync entries by default */
3020   sync = TRUE;
3021
3022   /* now map onto a fourcc, and some extra properties */
3023   if (strcmp (mimetype, "video/x-raw") == 0) {
3024     const gchar *format;
3025     GstVideoFormat fmt;
3026     const GstVideoFormatInfo *vinfo;
3027
3028     format = gst_structure_get_string (structure, "format");
3029     fmt = gst_video_format_from_string (format);
3030     vinfo = gst_video_format_get_info (fmt);
3031
3032     switch (fmt) {
3033       case GST_VIDEO_FORMAT_UYVY:
3034         if (depth == -1)
3035           depth = 24;
3036         entry.fourcc = FOURCC_2vuy;
3037         entry.depth = depth;
3038         sync = FALSE;
3039         break;
3040       default:
3041         if (GST_VIDEO_FORMAT_INFO_FLAGS (vinfo) & GST_VIDEO_FORMAT_FLAG_RGB) {
3042           entry.fourcc = FOURCC_raw_;
3043           entry.depth = GST_VIDEO_FORMAT_INFO_PSTRIDE (vinfo, 0) * 8;
3044           sync = FALSE;
3045         }
3046         break;
3047     }
3048   } else if (strcmp (mimetype, "video/x-h263") == 0) {
3049     ext_atom = NULL;
3050     if (format == GST_QT_MUX_FORMAT_QT)
3051       entry.fourcc = FOURCC_h263;
3052     else
3053       entry.fourcc = FOURCC_s263;
3054     ext_atom = build_h263_extension ();
3055     if (ext_atom != NULL)
3056       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
3057   } else if (strcmp (mimetype, "video/x-divx") == 0 ||
3058       strcmp (mimetype, "video/mpeg") == 0) {
3059     gint version = 0;
3060
3061     if (strcmp (mimetype, "video/x-divx") == 0) {
3062       gst_structure_get_int (structure, "divxversion", &version);
3063       version = version == 5 ? 1 : 0;
3064     } else {
3065       gst_structure_get_int (structure, "mpegversion", &version);
3066       version = version == 4 ? 1 : 0;
3067     }
3068     if (version) {
3069       entry.fourcc = FOURCC_mp4v;
3070       ext_atom =
3071           build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P2,
3072           ESDS_STREAM_TYPE_VISUAL, codec_data, qtpad->avg_bitrate,
3073           qtpad->max_bitrate);
3074       if (ext_atom != NULL)
3075         ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
3076       if (!codec_data)
3077         GST_WARNING_OBJECT (qtmux, "no codec_data for MPEG4 video; "
3078             "output might not play in Apple QuickTime (try global-headers?)");
3079     }
3080   } else if (strcmp (mimetype, "video/x-h264") == 0) {
3081     /* check if we accept these caps */
3082     if (gst_structure_has_field (structure, "stream-format")) {
3083       const gchar *format;
3084       const gchar *alignment;
3085
3086       format = gst_structure_get_string (structure, "stream-format");
3087       alignment = gst_structure_get_string (structure, "alignment");
3088
3089       if (strcmp (format, "avc") != 0 || alignment == NULL ||
3090           strcmp (alignment, "au") != 0) {
3091         GST_WARNING_OBJECT (qtmux, "Rejecting h264 caps, qtmux only accepts "
3092             "avc format with AU aligned samples");
3093         goto refuse_caps;
3094       }
3095     } else {
3096       GST_WARNING_OBJECT (qtmux, "no stream-format field in h264 caps");
3097       goto refuse_caps;
3098     }
3099
3100     if (!codec_data) {
3101       GST_WARNING_OBJECT (qtmux, "no codec_data in h264 caps");
3102       goto refuse_caps;
3103     }
3104
3105     entry.fourcc = FOURCC_avc1;
3106     if (qtpad->avg_bitrate == 0) {
3107       gint avg_bitrate = 0;
3108       gst_structure_get_int (structure, "bitrate", &avg_bitrate);
3109       qtpad->avg_bitrate = avg_bitrate;
3110     }
3111     ext_atom = build_btrt_extension (0, qtpad->avg_bitrate, qtpad->max_bitrate);
3112     if (ext_atom != NULL)
3113       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
3114     ext_atom = build_codec_data_extension (FOURCC_avcC, codec_data);
3115     if (ext_atom != NULL)
3116       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
3117   } else if (strcmp (mimetype, "video/x-svq") == 0) {
3118     gint version = 0;
3119     const GstBuffer *seqh = NULL;
3120     const GValue *seqh_value;
3121     gdouble gamma = 0;
3122
3123     gst_structure_get_int (structure, "svqversion", &version);
3124     if (version == 3) {
3125       entry.fourcc = FOURCC_SVQ3;
3126       entry.version = 3;
3127       entry.depth = 32;
3128
3129       seqh_value = gst_structure_get_value (structure, "seqh");
3130       if (seqh_value) {
3131         seqh = gst_value_get_buffer (seqh_value);
3132         ext_atom = build_SMI_atom (seqh);
3133         if (ext_atom)
3134           ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
3135       }
3136
3137       /* we need to add the gamma anyway because quicktime might crash
3138        * when it doesn't find it */
3139       if (!gst_structure_get_double (structure, "applied-gamma", &gamma)) {
3140         /* it seems that using 0 here makes it ignored */
3141         gamma = 0.0;
3142       }
3143       ext_atom = build_gama_atom (gamma);
3144       if (ext_atom)
3145         ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
3146     } else {
3147       GST_WARNING_OBJECT (qtmux, "SVQ version %d not supported. Please file "
3148           "a bug at http://bugzilla.gnome.org", version);
3149     }
3150   } else if (strcmp (mimetype, "video/x-dv") == 0) {
3151     gint version = 0;
3152     gboolean pal = TRUE;
3153
3154     sync = FALSE;
3155     if (framerate_num != 25 || framerate_den != 1)
3156       pal = FALSE;
3157     gst_structure_get_int (structure, "dvversion", &version);
3158     /* fall back to typical one */
3159     if (!version)
3160       version = 25;
3161     switch (version) {
3162       case 25:
3163         if (pal)
3164           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', 'p');
3165         else
3166           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', ' ');
3167         break;
3168       case 50:
3169         if (pal)
3170           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'p');
3171         else
3172           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'n');
3173         break;
3174       default:
3175         GST_WARNING_OBJECT (qtmux, "unrecognized dv version");
3176         break;
3177     }
3178   } else if (strcmp (mimetype, "image/jpeg") == 0) {
3179     entry.fourcc = FOURCC_jpeg;
3180     sync = FALSE;
3181   } else if (strcmp (mimetype, "image/x-j2c") == 0 ||
3182       strcmp (mimetype, "image/x-jpc") == 0) {
3183     const gchar *colorspace;
3184     const GValue *cmap_array;
3185     const GValue *cdef_array;
3186     gint ncomp = 0;
3187     gint fields = 1;
3188
3189     if (strcmp (mimetype, "image/x-jpc") == 0) {
3190       qtpad->prepare_buf_func = gst_qt_mux_prepare_jpc_buffer;
3191     }
3192
3193     gst_structure_get_int (structure, "num-components", &ncomp);
3194     gst_structure_get_int (structure, "fields", &fields);
3195     cmap_array = gst_structure_get_value (structure, "component-map");
3196     cdef_array = gst_structure_get_value (structure, "channel-definitions");
3197
3198     ext_atom = NULL;
3199     entry.fourcc = FOURCC_mjp2;
3200     sync = FALSE;
3201
3202     colorspace = gst_structure_get_string (structure, "colorspace");
3203     if (colorspace &&
3204         (ext_atom =
3205             build_jp2h_extension (qtpad->trak, width, height, colorspace, ncomp,
3206                 cmap_array, cdef_array)) != NULL) {
3207       ext_atom_list = g_list_append (ext_atom_list, ext_atom);
3208
3209       ext_atom = build_fiel_extension (fields);
3210       if (ext_atom)
3211         ext_atom_list = g_list_append (ext_atom_list, ext_atom);
3212
3213       ext_atom = build_jp2x_extension (codec_data);
3214       if (ext_atom)
3215         ext_atom_list = g_list_append (ext_atom_list, ext_atom);
3216     } else {
3217       GST_DEBUG_OBJECT (qtmux, "missing or invalid fourcc in jp2 caps");
3218       goto refuse_caps;
3219     }
3220   } else if (strcmp (mimetype, "video/x-vp8") == 0) {
3221     entry.fourcc = FOURCC_VP80;
3222     sync = FALSE;
3223   } else if (strcmp (mimetype, "video/x-dirac") == 0) {
3224     entry.fourcc = FOURCC_drac;
3225   } else if (strcmp (mimetype, "video/x-qt-part") == 0) {
3226     guint32 fourcc;
3227
3228     gst_structure_get_uint (structure, "format", &fourcc);
3229     entry.fourcc = fourcc;
3230   } else if (strcmp (mimetype, "video/x-mp4-part") == 0) {
3231     guint32 fourcc;
3232
3233     gst_structure_get_uint (structure, "format", &fourcc);
3234     entry.fourcc = fourcc;
3235   }
3236
3237   if (!entry.fourcc)
3238     goto refuse_caps;
3239
3240   /* ok, set the pad info accordingly */
3241   qtpad->fourcc = entry.fourcc;
3242   qtpad->sync = sync;
3243   atom_trak_set_video_type (qtpad->trak, qtmux->context, &entry, rate,
3244       ext_atom_list);
3245
3246   gst_object_unref (qtmux);
3247   return TRUE;
3248
3249   /* ERRORS */
3250 refuse_caps:
3251   {
3252     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
3253         GST_PAD_NAME (pad), caps);
3254     gst_object_unref (qtmux);
3255     return FALSE;
3256   }
3257 refuse_renegotiation:
3258   {
3259     GST_WARNING_OBJECT (qtmux,
3260         "pad %s refused renegotiation to %" GST_PTR_FORMAT, GST_PAD_NAME (pad),
3261         caps);
3262     gst_object_unref (qtmux);
3263     return FALSE;
3264   }
3265 }
3266
3267 static gboolean
3268 gst_qt_mux_subtitle_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
3269 {
3270   GstPad *pad = qtpad->collect.pad;
3271   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
3272   GstStructure *structure;
3273   SubtitleSampleEntry entry = { 0, };
3274
3275   qtpad->prepare_buf_func = NULL;
3276
3277   /* does not go well to renegotiate stream mid-way, unless
3278    * the old caps are a subset of the new one (this means upstream
3279    * added more info to the caps, as both should be 'fixed' caps) */
3280   if (qtpad->fourcc) {
3281     GstCaps *current_caps;
3282
3283     current_caps = gst_pad_get_current_caps (pad);
3284     g_assert (caps != NULL);
3285
3286     if (!gst_qtmux_caps_is_subset_full (qtmux, current_caps, caps)) {
3287       gst_caps_unref (current_caps);
3288       goto refuse_renegotiation;
3289     }
3290     GST_DEBUG_OBJECT (qtmux,
3291         "pad %s accepted renegotiation to %" GST_PTR_FORMAT " from %"
3292         GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, current_caps);
3293     gst_caps_unref (current_caps);
3294   }
3295
3296   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
3297       GST_DEBUG_PAD_NAME (pad), caps);
3298
3299   /* subtitles default */
3300   subtitle_sample_entry_init (&entry);
3301   qtpad->is_out_of_order = FALSE;
3302   qtpad->sync = FALSE;
3303   qtpad->sparse = TRUE;
3304   qtpad->prepare_buf_func = NULL;
3305
3306   structure = gst_caps_get_structure (caps, 0);
3307
3308   if (gst_structure_has_name (structure, "text/x-raw")) {
3309     const gchar *format = gst_structure_get_string (structure, "format");
3310     if (format && strcmp (format, "utf8") == 0) {
3311       entry.fourcc = FOURCC_tx3g;
3312       qtpad->prepare_buf_func = gst_qt_mux_prepare_tx3g_buffer;
3313       qtpad->create_empty_buffer = gst_qt_mux_create_empty_tx3g_buffer;
3314     }
3315   }
3316
3317   if (!entry.fourcc)
3318     goto refuse_caps;
3319
3320   qtpad->fourcc = entry.fourcc;
3321   atom_trak_set_subtitle_type (qtpad->trak, qtmux->context, &entry);
3322
3323   gst_object_unref (qtmux);
3324   return TRUE;
3325
3326   /* ERRORS */
3327 refuse_caps:
3328   {
3329     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
3330         GST_PAD_NAME (pad), caps);
3331     gst_object_unref (qtmux);
3332     return FALSE;
3333   }
3334 refuse_renegotiation:
3335   {
3336     GST_WARNING_OBJECT (qtmux,
3337         "pad %s refused renegotiation to %" GST_PTR_FORMAT, GST_PAD_NAME (pad),
3338         caps);
3339     gst_object_unref (qtmux);
3340     return FALSE;
3341   }
3342 }
3343
3344 static gboolean
3345 gst_qt_mux_sink_event (GstCollectPads * pads, GstCollectData * data,
3346     GstEvent * event, gpointer user_data)
3347 {
3348   GstQTMux *qtmux;
3349   guint32 avg_bitrate = 0, max_bitrate = 0;
3350   GstPad *pad = data->pad;
3351   gboolean ret = TRUE;
3352
3353   qtmux = GST_QT_MUX_CAST (user_data);
3354   switch (GST_EVENT_TYPE (event)) {
3355     case GST_EVENT_CAPS:
3356     {
3357       GstCaps *caps;
3358       GstQTPad *collect_pad;
3359
3360       gst_event_parse_caps (event, &caps);
3361
3362       /* find stream data */
3363       collect_pad = (GstQTPad *) gst_pad_get_element_private (pad);
3364       g_assert (collect_pad);
3365       g_assert (collect_pad->set_caps);
3366
3367       ret = collect_pad->set_caps (collect_pad, caps);
3368       gst_event_unref (event);
3369       event = NULL;
3370       break;
3371     }
3372     case GST_EVENT_TAG:{
3373       GstTagList *list;
3374       GstTagSetter *setter = GST_TAG_SETTER (qtmux);
3375       GstTagMergeMode mode;
3376       gchar *code;
3377
3378       GST_OBJECT_LOCK (qtmux);
3379       mode = gst_tag_setter_get_tag_merge_mode (setter);
3380
3381       gst_event_parse_tag (event, &list);
3382       GST_DEBUG_OBJECT (qtmux, "received tag event on pad %s:%s : %"
3383           GST_PTR_FORMAT, GST_DEBUG_PAD_NAME (pad), list);
3384
3385       gst_tag_setter_merge_tags (setter, list, mode);
3386       GST_OBJECT_UNLOCK (qtmux);
3387
3388       if (gst_tag_list_get_uint (list, GST_TAG_BITRATE, &avg_bitrate) |
3389           gst_tag_list_get_uint (list, GST_TAG_MAXIMUM_BITRATE, &max_bitrate)) {
3390         GstQTPad *qtpad = gst_pad_get_element_private (pad);
3391         g_assert (qtpad);
3392
3393         if (avg_bitrate > 0 && avg_bitrate < G_MAXUINT32)
3394           qtpad->avg_bitrate = avg_bitrate;
3395         if (max_bitrate > 0 && max_bitrate < G_MAXUINT32)
3396           qtpad->max_bitrate = max_bitrate;
3397       }
3398
3399       if (gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &code)) {
3400         const char *iso_code = gst_tag_get_language_code_iso_639_2T (code);
3401         if (iso_code) {
3402           GstQTPad *qtpad = gst_pad_get_element_private (pad);
3403           g_assert (qtpad);
3404           if (qtpad->trak) {
3405             /* https://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap4/qtff4.html */
3406             qtpad->trak->mdia.mdhd.language_code =
3407                 (iso_code[0] - 0x60) * 0x400 + (iso_code[1] - 0x60) * 0x20 +
3408                 (iso_code[2] - 0x60);
3409           }
3410         }
3411         g_free (code);
3412       }
3413
3414       gst_event_unref (event);
3415       event = NULL;
3416       ret = TRUE;
3417       break;
3418     }
3419     default:
3420       break;
3421   }
3422
3423   if (event != NULL)
3424     return gst_collect_pads_event_default (pads, data, event, FALSE);
3425
3426   return ret;
3427 }
3428
3429 static void
3430 gst_qt_mux_release_pad (GstElement * element, GstPad * pad)
3431 {
3432   GstQTMux *mux = GST_QT_MUX_CAST (element);
3433   GSList *walk;
3434
3435   GST_DEBUG_OBJECT (element, "Releasing %s:%s", GST_DEBUG_PAD_NAME (pad));
3436
3437   for (walk = mux->sinkpads; walk; walk = g_slist_next (walk)) {
3438     GstQTPad *qtpad = (GstQTPad *) walk->data;
3439     GST_DEBUG ("Checking %s:%s", GST_DEBUG_PAD_NAME (qtpad->collect.pad));
3440     if (qtpad->collect.pad == pad) {
3441       /* this is it, remove */
3442       mux->sinkpads = g_slist_delete_link (mux->sinkpads, walk);
3443       gst_element_remove_pad (element, pad);
3444       break;
3445     }
3446   }
3447
3448   gst_collect_pads_remove_pad (mux->collect, pad);
3449 }
3450
3451 static GstPad *
3452 gst_qt_mux_request_new_pad (GstElement * element,
3453     GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps)
3454 {
3455   GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
3456   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
3457   GstQTPad *collect_pad;
3458   GstPad *newpad;
3459   GstQTPadSetCapsFunc setcaps_func;
3460   gchar *name;
3461   gint pad_id;
3462
3463   if (templ->direction != GST_PAD_SINK)
3464     goto wrong_direction;
3465
3466   if (qtmux->state > GST_QT_MUX_STATE_STARTED)
3467     goto too_late;
3468
3469   if (templ == gst_element_class_get_pad_template (klass, "audio_%u")) {
3470     setcaps_func = gst_qt_mux_audio_sink_set_caps;
3471     if (req_name != NULL && sscanf (req_name, "audio_%u", &pad_id) == 1) {
3472       name = g_strdup (req_name);
3473     } else {
3474       name = g_strdup_printf ("audio_%u", qtmux->audio_pads++);
3475     }
3476   } else if (templ == gst_element_class_get_pad_template (klass, "video_%u")) {
3477     setcaps_func = gst_qt_mux_video_sink_set_caps;
3478     if (req_name != NULL && sscanf (req_name, "video_%u", &pad_id) == 1) {
3479       name = g_strdup (req_name);
3480     } else {
3481       name = g_strdup_printf ("video_%u", qtmux->video_pads++);
3482     }
3483   } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%u")) {
3484     setcaps_func = gst_qt_mux_subtitle_sink_set_caps;
3485     if (req_name != NULL && sscanf (req_name, "subtitle_%u", &pad_id) == 1) {
3486       name = g_strdup (req_name);
3487     } else {
3488       name = g_strdup_printf ("subtitle_%u", qtmux->subtitle_pads++);
3489     }
3490   } else
3491     goto wrong_template;
3492
3493   GST_DEBUG_OBJECT (qtmux, "Requested pad: %s", name);
3494
3495   /* create pad and add to collections */
3496   newpad = gst_pad_new_from_template (templ, name);
3497   g_free (name);
3498   collect_pad = (GstQTPad *)
3499       gst_collect_pads_add_pad (qtmux->collect, newpad, sizeof (GstQTPad),
3500       (GstCollectDataDestroyNotify) (gst_qt_mux_pad_reset), TRUE);
3501   /* set up pad */
3502   gst_qt_mux_pad_reset (collect_pad);
3503   collect_pad->trak = atom_trak_new (qtmux->context);
3504   atom_moov_add_trak (qtmux->moov, collect_pad->trak);
3505
3506   qtmux->sinkpads = g_slist_append (qtmux->sinkpads, collect_pad);
3507
3508   /* set up pad functions */
3509   collect_pad->set_caps = setcaps_func;
3510
3511   gst_pad_set_active (newpad, TRUE);
3512   gst_element_add_pad (element, newpad);
3513
3514   return newpad;
3515
3516   /* ERRORS */
3517 wrong_direction:
3518   {
3519     GST_WARNING_OBJECT (qtmux, "Request pad that is not a SINK pad.");
3520     return NULL;
3521   }
3522 too_late:
3523   {
3524     GST_WARNING_OBJECT (qtmux, "Not providing request pad after stream start.");
3525     return NULL;
3526   }
3527 wrong_template:
3528   {
3529     GST_WARNING_OBJECT (qtmux, "This is not our template!");
3530     return NULL;
3531   }
3532 }
3533
3534 static void
3535 gst_qt_mux_get_property (GObject * object,
3536     guint prop_id, GValue * value, GParamSpec * pspec)
3537 {
3538   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
3539
3540   GST_OBJECT_LOCK (qtmux);
3541   switch (prop_id) {
3542     case PROP_MOVIE_TIMESCALE:
3543       g_value_set_uint (value, qtmux->timescale);
3544       break;
3545     case PROP_TRAK_TIMESCALE:
3546       g_value_set_uint (value, qtmux->trak_timescale);
3547       break;
3548     case PROP_DO_CTTS:
3549       g_value_set_boolean (value, qtmux->guess_pts);
3550       break;
3551 #ifndef GST_REMOVE_DEPRECATED
3552     case PROP_DTS_METHOD:
3553       g_value_set_enum (value, qtmux->dts_method);
3554       break;
3555 #endif
3556     case PROP_FAST_START:
3557       g_value_set_boolean (value, qtmux->fast_start);
3558       break;
3559     case PROP_FAST_START_TEMP_FILE:
3560       g_value_set_string (value, qtmux->fast_start_file_path);
3561       break;
3562     case PROP_MOOV_RECOV_FILE:
3563       g_value_set_string (value, qtmux->moov_recov_file_path);
3564       break;
3565     case PROP_FRAGMENT_DURATION:
3566       g_value_set_uint (value, qtmux->fragment_duration);
3567       break;
3568     case PROP_STREAMABLE:
3569       g_value_set_boolean (value, qtmux->streamable);
3570       break;
3571     default:
3572       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3573       break;
3574   }
3575   GST_OBJECT_UNLOCK (qtmux);
3576 }
3577
3578 static void
3579 gst_qt_mux_generate_fast_start_file_path (GstQTMux * qtmux)
3580 {
3581   gchar *tmp;
3582
3583   g_free (qtmux->fast_start_file_path);
3584   qtmux->fast_start_file_path = NULL;
3585
3586   tmp = g_strdup_printf ("%s%d", "qtmux", g_random_int ());
3587   qtmux->fast_start_file_path = g_build_filename (g_get_tmp_dir (), tmp, NULL);
3588   g_free (tmp);
3589 }
3590
3591 static void
3592 gst_qt_mux_set_property (GObject * object,
3593     guint prop_id, const GValue * value, GParamSpec * pspec)
3594 {
3595   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
3596
3597   GST_OBJECT_LOCK (qtmux);
3598   switch (prop_id) {
3599     case PROP_MOVIE_TIMESCALE:
3600       qtmux->timescale = g_value_get_uint (value);
3601       break;
3602     case PROP_TRAK_TIMESCALE:
3603       qtmux->trak_timescale = g_value_get_uint (value);
3604       break;
3605     case PROP_DO_CTTS:
3606       qtmux->guess_pts = g_value_get_boolean (value);
3607       break;
3608 #ifndef GST_REMOVE_DEPRECATED
3609     case PROP_DTS_METHOD:
3610       qtmux->dts_method = g_value_get_enum (value);
3611       break;
3612 #endif
3613     case PROP_FAST_START:
3614       qtmux->fast_start = g_value_get_boolean (value);
3615       break;
3616     case PROP_FAST_START_TEMP_FILE:
3617       g_free (qtmux->fast_start_file_path);
3618       qtmux->fast_start_file_path = g_value_dup_string (value);
3619       /* NULL means to generate a random one */
3620       if (!qtmux->fast_start_file_path) {
3621         gst_qt_mux_generate_fast_start_file_path (qtmux);
3622       }
3623       break;
3624     case PROP_MOOV_RECOV_FILE:
3625       g_free (qtmux->moov_recov_file_path);
3626       qtmux->moov_recov_file_path = g_value_dup_string (value);
3627       break;
3628     case PROP_FRAGMENT_DURATION:
3629       qtmux->fragment_duration = g_value_get_uint (value);
3630       break;
3631     case PROP_STREAMABLE:{
3632       GstQTMuxClass *qtmux_klass =
3633           (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
3634       if (qtmux_klass->format == GST_QT_MUX_FORMAT_ISML) {
3635         qtmux->streamable = g_value_get_boolean (value);
3636       }
3637       break;
3638     }
3639     default:
3640       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3641       break;
3642   }
3643   GST_OBJECT_UNLOCK (qtmux);
3644 }
3645
3646 static GstStateChangeReturn
3647 gst_qt_mux_change_state (GstElement * element, GstStateChange transition)
3648 {
3649   GstStateChangeReturn ret;
3650   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
3651
3652   switch (transition) {
3653     case GST_STATE_CHANGE_NULL_TO_READY:
3654       break;
3655     case GST_STATE_CHANGE_READY_TO_PAUSED:
3656       gst_collect_pads_start (qtmux->collect);
3657       qtmux->state = GST_QT_MUX_STATE_STARTED;
3658       break;
3659     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
3660       break;
3661     case GST_STATE_CHANGE_PAUSED_TO_READY:
3662       gst_collect_pads_stop (qtmux->collect);
3663       break;
3664     default:
3665       break;
3666   }
3667
3668   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
3669
3670   switch (transition) {
3671     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
3672       break;
3673     case GST_STATE_CHANGE_PAUSED_TO_READY:
3674       gst_qt_mux_reset (qtmux, TRUE);
3675       break;
3676     case GST_STATE_CHANGE_READY_TO_NULL:
3677       break;
3678     default:
3679       break;
3680   }
3681
3682   return ret;
3683 }
3684
3685 gboolean
3686 gst_qt_mux_register (GstPlugin * plugin)
3687 {
3688   GTypeInfo typeinfo = {
3689     sizeof (GstQTMuxClass),
3690     (GBaseInitFunc) gst_qt_mux_base_init,
3691     NULL,
3692     (GClassInitFunc) gst_qt_mux_class_init,
3693     NULL,
3694     NULL,
3695     sizeof (GstQTMux),
3696     0,
3697     (GInstanceInitFunc) gst_qt_mux_init,
3698   };
3699   static const GInterfaceInfo tag_setter_info = {
3700     NULL, NULL, NULL
3701   };
3702   static const GInterfaceInfo tag_xmp_writer_info = {
3703     NULL, NULL, NULL
3704   };
3705   GType type;
3706   GstQTMuxFormat format;
3707   GstQTMuxClassParams *params;
3708   guint i = 0;
3709
3710   GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
3711
3712   GST_LOG ("Registering muxers");
3713
3714   while (TRUE) {
3715     GstQTMuxFormatProp *prop;
3716     GstCaps *subtitle_caps;
3717
3718     prop = &gst_qt_mux_format_list[i];
3719     format = prop->format;
3720     if (format == GST_QT_MUX_FORMAT_NONE)
3721       break;
3722
3723     /* create a cache for these properties */
3724     params = g_new0 (GstQTMuxClassParams, 1);
3725     params->prop = prop;
3726     params->src_caps = gst_static_caps_get (&prop->src_caps);
3727     params->video_sink_caps = gst_static_caps_get (&prop->video_sink_caps);
3728     params->audio_sink_caps = gst_static_caps_get (&prop->audio_sink_caps);
3729     subtitle_caps = gst_static_caps_get (&prop->subtitle_sink_caps);
3730     if (!gst_caps_is_equal (subtitle_caps, GST_CAPS_NONE)) {
3731       params->subtitle_sink_caps = subtitle_caps;
3732     } else {
3733       gst_caps_unref (subtitle_caps);
3734     }
3735
3736     /* create the type now */
3737     type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo,
3738         0);
3739     g_type_set_qdata (type, GST_QT_MUX_PARAMS_QDATA, (gpointer) params);
3740     g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
3741     g_type_add_interface_static (type, GST_TYPE_TAG_XMP_WRITER,
3742         &tag_xmp_writer_info);
3743
3744     if (!gst_element_register (plugin, prop->name, prop->rank, type))
3745       return FALSE;
3746
3747     i++;
3748   }
3749
3750   GST_LOG ("Finished registering muxers");
3751
3752   /* FIXME: ideally classification tag should be added and
3753      registered in gstreamer core gsttaglist
3754    */
3755
3756   GST_LOG ("Registering tags");
3757
3758   gst_tag_register (GST_TAG_3GP_CLASSIFICATION, GST_TAG_FLAG_META,
3759       G_TYPE_STRING, GST_TAG_3GP_CLASSIFICATION, "content classification",
3760       gst_tag_merge_use_first);
3761
3762   GST_LOG ("Finished registering tags");
3763
3764   return TRUE;
3765 }