qtmux: Error out on discontinuities/gaps when muxing raw audio
[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  * Copyright (C) 2014 Jan Schmidt <jan@centricular.com>
6  * Contact: Stefan Kost <stefan.kost@nokia.com>
7
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23 /*
24  * Unless otherwise indicated, Source Code is licensed under MIT license.
25  * See further explanation attached in License Statement (distributed in the file
26  * LICENSE).
27  *
28  * Permission is hereby granted, free of charge, to any person obtaining a copy of
29  * this software and associated documentation files (the "Software"), to deal in
30  * the Software without restriction, including without limitation the rights to
31  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
32  * of the Software, and to permit persons to whom the Software is furnished to do
33  * so, subject to the following conditions:
34  *
35  * The above copyright notice and this permission notice shall be included in all
36  * copies or substantial portions of the Software.
37  *
38  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44  * SOFTWARE.
45  */
46
47
48 /**
49  * SECTION:element-qtmux
50  * @short_description: Muxer for quicktime(.mov) files
51  *
52  * This element merges streams (audio and video) into QuickTime(.mov) files.
53  *
54  * The following background intends to explain why various similar muxers
55  * are present in this plugin.
56  *
57  * The <ulink url="http://www.apple.com/quicktime/resources/qtfileformat.pdf">
58  * QuickTime file format specification</ulink> served as basis for the MP4 file
59  * format specification (mp4mux), and as such the QuickTime file structure is
60  * nearly identical to the so-called ISO Base Media file format defined in
61  * ISO 14496-12 (except for some media specific parts).
62  * In turn, the latter ISO Base Media format was further specialized as a
63  * Motion JPEG-2000 file format in ISO 15444-3 (mj2mux)
64  * and in various 3GPP(2) specs (gppmux).
65  * The fragmented file features defined (only) in ISO Base Media are used by
66  * ISMV files making up (a.o.) Smooth Streaming (ismlmux).
67  *
68  * A few properties (#GstQTMux:movie-timescale, #GstQTMux:trak-timescale) allow
69  * adjusting some technical parameters, which might be useful in (rare) cases to
70  * resolve compatibility issues in some situations.
71  *
72  * Some other properties influence the result more fundamentally.
73  * A typical mov/mp4 file's metadata (aka moov) is located at the end of the
74  * file, somewhat contrary to this usually being called "the header".
75  * However, a #GstQTMux:faststart file will (with some effort) arrange this to
76  * be located near start of the file, which then allows it e.g. to be played
77  * while downloading. Alternatively, rather than having one chunk of metadata at
78  * start (or end), there can be some metadata at start and most of the other
79  * data can be spread out into fragments of #GstQTMux:fragment-duration.
80  * If such fragmented layout is intended for streaming purposes, then
81  * #GstQTMux:streamable allows foregoing to add index metadata (at the end of
82  * file).
83  *
84  * When the maximum duration to be recorded can be known in advance, #GstQTMux
85  * also supports a 'Robust Muxing' mode. In robust muxing mode,  space for the
86  * headers are reserved at the start of muxing, and rewritten at a configurable
87  * interval, so that the output file is always playable, even if the recording
88  * is interrupted uncleanly by a crash. Robust muxing mode requires a seekable
89  * output, such as filesink, because it needs to rewrite the start of the file.
90  *
91  * To enable robust muxing mode, set the #GstQTMux::reserved-moov-update-period
92  * and #GstQTMux::reserved-max-duration property. Also present is the
93  * #GstQTMux::reserved-bytes-per-sec property, which can be increased if
94  * for some reason the default is not large enough and the initial reserved
95  * space for headers is too small. Applications can monitor the
96  * #GstQTMux::reserved-duration-remaining property to see how close to full
97  * the reserved space is becoming.
98  *
99  * <refsect2>
100  * <title>Example pipelines</title>
101  * |[
102  * gst-launch-1.0 v4l2src num-buffers=500 ! video/x-raw,width=320,height=240 ! videoconvert ! qtmux ! filesink location=video.mov
103  * ]|
104  * Records a video stream captured from a v4l2 device and muxes it into a qt file.
105  * </refsect2>
106  */
107
108 /*
109  * Based on avimux
110  */
111
112 #ifdef HAVE_CONFIG_H
113 #include "config.h"
114 #endif
115
116 #include <glib/gstdio.h>
117
118 #include <gst/gst.h>
119 #include <gst/base/gstcollectpads.h>
120 #include <gst/base/gstbytereader.h>
121 #include <gst/base/gstbitreader.h>
122 #include <gst/audio/audio.h>
123 #include <gst/video/video.h>
124 #include <gst/tag/tag.h>
125 #include <gst/pbutils/pbutils.h>
126
127 #include <sys/types.h>
128 #ifdef G_OS_WIN32
129 #include <io.h>                 /* lseek, open, close, read */
130 #undef lseek
131 #define lseek _lseeki64
132 #undef off_t
133 #define off_t guint64
134 #endif
135
136 #ifdef _MSC_VER
137 #define ftruncate g_win32_ftruncate
138 #endif
139
140 #ifdef HAVE_UNISTD_H
141 #  include <unistd.h>
142 #endif
143
144 #include "gstqtmux.h"
145
146 GST_DEBUG_CATEGORY_STATIC (gst_qt_mux_debug);
147 #define GST_CAT_DEFAULT gst_qt_mux_debug
148
149 #ifndef ABSDIFF
150 #define ABSDIFF(a, b) ((a) > (b) ? (a) - (b) : (b) - (a))
151 #endif
152
153 /* Hacker notes.
154  *
155  * The basic building blocks of MP4 files are:
156  *  - an 'ftyp' box at the very start
157  *  - an 'mdat' box which contains the raw audio/video/subtitle data;
158  *    this is just a bunch of bytes, completely unframed and possibly
159  *    unordered with no additional meta-information
160  *  - a 'moov' box that contains information about the different streams
161  *    and what they contain, as well as sample tables for each stream
162  *    that tell the demuxer where in the mdat box each buffer/sample is
163  *    and what its duration/timestamp etc. is, and whether it's a
164  *    keyframe etc.
165  * Additionally, fragmented MP4 works by writing chunks of data in
166  * pairs of 'moof' and 'mdat' boxes:
167  *  - 'moof' boxes, header preceding each mdat fragment describing the
168  *    contents, like a moov but only for that fragment.
169  *  - a 'mfra' box for Fragmented MP4, which is written at the end and
170  *    contains a summary of all fragments and seek tables.
171  *
172  * Currently mp4mux can work in 4 different modes / generate 4 types
173  * of output files/streams:
174  *
175  * - Normal mp4: mp4mux will write a little ftyp identifier at the
176  *   beginning, then start an mdat box into which it will write all the
177  *   sample data. At EOS it will then write the moov header with track
178  *   headers and sample tables at the end of the file, and rewrite the
179  *   start of the file to fix up the mdat box size at the beginning.
180  *   It has to wait for EOS to write the moov (which includes the
181  *   sample tables) because it doesn't know how much space those
182  *   tables will be. The output downstream must be seekable to rewrite
183  *   the mdat box at EOS.
184  *
185  * - Fragmented mp4: moov header with track headers at start
186  *   but no sample table, followed by N fragments, each containing
187  *   track headers with sample tables followed by some data. Downstream
188  *   does not need to be seekable if the 'streamable' flag is TRUE,
189  *   as the final mfra and total duration will be omitted.
190  *
191  * - Fast-start mp4: the goal here is to create a file where the moov
192  *   headers are at the beginning; what mp4mux will do is write all
193  *   sample data into a temp file and build moov header plus sample
194  *   tables in memory and then when EOS comes, it will push out the
195  *   moov header plus sample tables at the beginning, followed by the
196  *   mdat sample data at the end which is read in from the temp file
197  *   Files created in this mode are better for streaming over the
198  *   network, since the client doesn't have to seek to the end of the
199  *   file to get the headers, but it requires copying all sample data
200  *   out of the temp file at EOS, which can be expensive. Downstream does
201  *   not need to be seekable, because of the use of the temp file.
202  *
203  * - Robust Muxing mode: In this mode, qtmux uses the reserved-max-duration
204  *   and reserved-moov-update-period properties to reserve free space
205  *   at the start of the file and periodically write the MOOV atom out
206  *   to it. That means that killing the muxing at any point still
207  *   results in a playable file, at the cost of wasting some amount of
208  *   free space at the start of file. The approximate recording duration
209  *   has to be known in advance to estimate how much free space to reserve
210  *   for the moov, and the downstream must be seekable.
211  *   If the moov header grows larger than the reserved space, an error
212  *   is generated - so it's better to over-estimate the amount of space
213  *   to reserve. To ensure the file is playable at any point, the moov
214  *   is updated using a 'ping-pong' strategy, so the output is never in
215  *   an invalid state.
216  */
217
218 #ifndef GST_REMOVE_DEPRECATED
219 enum
220 {
221   DTS_METHOD_DD,
222   DTS_METHOD_REORDER,
223   DTS_METHOD_ASC
224 };
225
226 static GType
227 gst_qt_mux_dts_method_get_type (void)
228 {
229   static GType gst_qt_mux_dts_method = 0;
230
231   if (!gst_qt_mux_dts_method) {
232     static const GEnumValue dts_methods[] = {
233       {DTS_METHOD_DD, "delta/duration", "dd"},
234       {DTS_METHOD_REORDER, "reorder", "reorder"},
235       {DTS_METHOD_ASC, "ascending", "asc"},
236       {0, NULL, NULL},
237     };
238
239     gst_qt_mux_dts_method =
240         g_enum_register_static ("GstQTMuxDtsMethods", dts_methods);
241   }
242
243   return gst_qt_mux_dts_method;
244 }
245
246 #define GST_TYPE_QT_MUX_DTS_METHOD \
247   (gst_qt_mux_dts_method_get_type ())
248 #endif
249
250 /* QTMux signals and args */
251 enum
252 {
253   /* FILL ME */
254   LAST_SIGNAL
255 };
256
257 enum
258 {
259   PROP_0,
260   PROP_MOVIE_TIMESCALE,
261   PROP_TRAK_TIMESCALE,
262   PROP_FAST_START,
263   PROP_FAST_START_TEMP_FILE,
264   PROP_MOOV_RECOV_FILE,
265   PROP_FRAGMENT_DURATION,
266   PROP_STREAMABLE,
267   PROP_RESERVED_MAX_DURATION,
268   PROP_RESERVED_DURATION_REMAINING,
269   PROP_RESERVED_MOOV_UPDATE_PERIOD,
270   PROP_RESERVED_BYTES_PER_SEC,
271 #ifndef GST_REMOVE_DEPRECATED
272   PROP_DTS_METHOD,
273 #endif
274   PROP_DO_CTTS,
275   PROP_INTERLEAVE_BYTES,
276   PROP_INTERLEAVE_TIME,
277   PROP_MAX_RAW_AUDIO_DRIFT,
278 };
279
280 /* some spare for header size as well */
281 #define MDAT_LARGE_FILE_LIMIT           ((guint64) 1024 * 1024 * 1024 * 2)
282
283 #define DEFAULT_MOVIE_TIMESCALE         0
284 #define DEFAULT_TRAK_TIMESCALE          0
285 #define DEFAULT_DO_CTTS                 TRUE
286 #define DEFAULT_FAST_START              FALSE
287 #define DEFAULT_FAST_START_TEMP_FILE    NULL
288 #define DEFAULT_MOOV_RECOV_FILE         NULL
289 #define DEFAULT_FRAGMENT_DURATION       0
290 #define DEFAULT_STREAMABLE              TRUE
291 #ifndef GST_REMOVE_DEPRECATED
292 #define DEFAULT_DTS_METHOD              DTS_METHOD_REORDER
293 #endif
294 #define DEFAULT_RESERVED_MAX_DURATION   GST_CLOCK_TIME_NONE
295 #define DEFAULT_RESERVED_MOOV_UPDATE_PERIOD   GST_CLOCK_TIME_NONE
296 #define DEFAULT_RESERVED_BYTES_PER_SEC_PER_TRAK 550
297 #define DEFAULT_INTERLEAVE_BYTES 0
298 #define DEFAULT_INTERLEAVE_TIME 250*GST_MSECOND
299 #define DEFAULT_MAX_RAW_AUDIO_DRIFT 40 * GST_MSECOND
300
301 static void gst_qt_mux_finalize (GObject * object);
302
303 static GstStateChangeReturn gst_qt_mux_change_state (GstElement * element,
304     GstStateChange transition);
305
306 /* property functions */
307 static void gst_qt_mux_set_property (GObject * object,
308     guint prop_id, const GValue * value, GParamSpec * pspec);
309 static void gst_qt_mux_get_property (GObject * object,
310     guint prop_id, GValue * value, GParamSpec * pspec);
311
312 /* pad functions */
313 static GstPad *gst_qt_mux_request_new_pad (GstElement * element,
314     GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
315 static void gst_qt_mux_release_pad (GstElement * element, GstPad * pad);
316
317 /* event */
318 static gboolean gst_qt_mux_sink_event (GstCollectPads * pads,
319     GstCollectData * data, GstEvent * event, gpointer user_data);
320
321 static GstFlowReturn gst_qt_mux_collected (GstCollectPads * pads,
322     gpointer user_data);
323 static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
324     GstBuffer * buf);
325
326 static GstFlowReturn
327 gst_qt_mux_robust_recording_rewrite_moov (GstQTMux * qtmux);
328
329 static GstElementClass *parent_class = NULL;
330
331 static void
332 gst_qt_mux_base_init (gpointer g_class)
333 {
334   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
335   GstQTMuxClass *klass = (GstQTMuxClass *) g_class;
336   GstQTMuxClassParams *params;
337   GstPadTemplate *videosinktempl, *audiosinktempl, *subtitlesinktempl;
338   GstPadTemplate *srctempl;
339   gchar *longname, *description;
340
341   params =
342       (GstQTMuxClassParams *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (g_class),
343       GST_QT_MUX_PARAMS_QDATA);
344   g_assert (params != NULL);
345
346   /* construct the element details struct */
347   longname = g_strdup_printf ("%s Muxer", params->prop->long_name);
348   description = g_strdup_printf ("Multiplex audio and video into a %s file",
349       params->prop->long_name);
350   gst_element_class_set_static_metadata (element_class, longname,
351       "Codec/Muxer", description,
352       "Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>");
353   g_free (longname);
354   g_free (description);
355
356   /* pad templates */
357   srctempl = gst_pad_template_new ("src", GST_PAD_SRC,
358       GST_PAD_ALWAYS, params->src_caps);
359   gst_element_class_add_pad_template (element_class, srctempl);
360
361   if (params->audio_sink_caps) {
362     audiosinktempl = gst_pad_template_new ("audio_%u",
363         GST_PAD_SINK, GST_PAD_REQUEST, params->audio_sink_caps);
364     gst_element_class_add_pad_template (element_class, audiosinktempl);
365   }
366
367   if (params->video_sink_caps) {
368     videosinktempl = gst_pad_template_new ("video_%u",
369         GST_PAD_SINK, GST_PAD_REQUEST, params->video_sink_caps);
370     gst_element_class_add_pad_template (element_class, videosinktempl);
371   }
372
373   if (params->subtitle_sink_caps) {
374     subtitlesinktempl = gst_pad_template_new ("subtitle_%u",
375         GST_PAD_SINK, GST_PAD_REQUEST, params->subtitle_sink_caps);
376     gst_element_class_add_pad_template (element_class, subtitlesinktempl);
377   }
378
379   klass->format = params->prop->format;
380 }
381
382 static void
383 gst_qt_mux_class_init (GstQTMuxClass * klass)
384 {
385   GObjectClass *gobject_class;
386   GstElementClass *gstelement_class;
387   GParamFlags streamable_flags;
388   const gchar *streamable_desc;
389   gboolean streamable;
390 #define STREAMABLE_DESC "If set to true, the output should be as if it is to "\
391   "be streamed and hence no indexes written or duration written."
392
393   gobject_class = (GObjectClass *) klass;
394   gstelement_class = (GstElementClass *) klass;
395
396   parent_class = g_type_class_peek_parent (klass);
397
398   gobject_class->finalize = gst_qt_mux_finalize;
399   gobject_class->get_property = gst_qt_mux_get_property;
400   gobject_class->set_property = gst_qt_mux_set_property;
401
402   streamable_flags = G_PARAM_READWRITE | G_PARAM_CONSTRUCT;
403   if (klass->format == GST_QT_MUX_FORMAT_ISML) {
404     streamable_desc = STREAMABLE_DESC;
405     streamable = DEFAULT_STREAMABLE;
406   } else {
407     streamable_desc =
408         STREAMABLE_DESC " (DEPRECATED, only valid for fragmented MP4)";
409     streamable_flags |= G_PARAM_DEPRECATED;
410     streamable = FALSE;
411   }
412
413   g_object_class_install_property (gobject_class, PROP_MOVIE_TIMESCALE,
414       g_param_spec_uint ("movie-timescale", "Movie timescale",
415           "Timescale to use in the movie (units per second, 0 == default)",
416           0, G_MAXUINT32, DEFAULT_MOVIE_TIMESCALE,
417           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
418   g_object_class_install_property (gobject_class, PROP_TRAK_TIMESCALE,
419       g_param_spec_uint ("trak-timescale", "Track timescale",
420           "Timescale to use for the tracks (units per second, 0 is automatic)",
421           0, G_MAXUINT32, DEFAULT_TRAK_TIMESCALE,
422           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
423   g_object_class_install_property (gobject_class, PROP_DO_CTTS,
424       g_param_spec_boolean ("presentation-time",
425           "Include presentation-time info",
426           "Calculate and include presentation/composition time "
427           "(in addition to decoding time)", DEFAULT_DO_CTTS,
428           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
429 #ifndef GST_REMOVE_DEPRECATED
430   g_object_class_install_property (gobject_class, PROP_DTS_METHOD,
431       g_param_spec_enum ("dts-method", "dts-method",
432           "Method to determine DTS time (DEPRECATED)",
433           GST_TYPE_QT_MUX_DTS_METHOD, DEFAULT_DTS_METHOD,
434           G_PARAM_DEPRECATED | G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
435           G_PARAM_STATIC_STRINGS));
436 #endif
437   g_object_class_install_property (gobject_class, PROP_FAST_START,
438       g_param_spec_boolean ("faststart", "Format file to faststart",
439           "If the file should be formatted for faststart (headers first)",
440           DEFAULT_FAST_START, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
441   g_object_class_install_property (gobject_class, PROP_FAST_START_TEMP_FILE,
442       g_param_spec_string ("faststart-file", "File to use for storing buffers",
443           "File that will be used temporarily to store data from the stream "
444           "when creating a faststart file. If null a filepath will be "
445           "created automatically", DEFAULT_FAST_START_TEMP_FILE,
446           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
447   g_object_class_install_property (gobject_class, PROP_MOOV_RECOV_FILE,
448       g_param_spec_string ("moov-recovery-file",
449           "File to store data for posterior moov atom recovery",
450           "File to be used to store "
451           "data for moov atom making movie file recovery possible in case "
452           "of a crash during muxing. Null for disabled. (Experimental)",
453           DEFAULT_MOOV_RECOV_FILE,
454           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
455   g_object_class_install_property (gobject_class, PROP_FRAGMENT_DURATION,
456       g_param_spec_uint ("fragment-duration", "Fragment duration",
457           "Fragment durations in ms (produce a fragmented file if > 0)",
458           0, G_MAXUINT32, klass->format == GST_QT_MUX_FORMAT_ISML ?
459           2000 : DEFAULT_FRAGMENT_DURATION,
460           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
461   g_object_class_install_property (gobject_class, PROP_STREAMABLE,
462       g_param_spec_boolean ("streamable", "Streamable", streamable_desc,
463           streamable, streamable_flags | G_PARAM_STATIC_STRINGS));
464   g_object_class_install_property (gobject_class, PROP_RESERVED_MAX_DURATION,
465       g_param_spec_uint64 ("reserved-max-duration",
466           "Reserved maximum file duration (ns)",
467           "When set to a value > 0, reserves space for index tables at the "
468           "beginning of the file.",
469           0, G_MAXUINT64, DEFAULT_RESERVED_MAX_DURATION,
470           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
471   g_object_class_install_property (gobject_class,
472       PROP_RESERVED_DURATION_REMAINING,
473       g_param_spec_uint64 ("reserved-duration-remaining",
474           "Report the approximate amount of remaining recording space (ns)",
475           "Reports the approximate amount of remaining moov header space "
476           "reserved using reserved-max-duration", 0, G_MAXUINT64, 0,
477           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
478   g_object_class_install_property (gobject_class,
479       PROP_RESERVED_MOOV_UPDATE_PERIOD,
480       g_param_spec_uint64 ("reserved-moov-update-period",
481           "Interval at which to update index tables (ns)",
482           "When used with reserved-max-duration, periodically updates the "
483           "index tables with information muxed so far.", 0, G_MAXUINT64,
484           DEFAULT_RESERVED_MOOV_UPDATE_PERIOD,
485           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
486   g_object_class_install_property (gobject_class, PROP_RESERVED_BYTES_PER_SEC,
487       g_param_spec_uint ("reserved-bytes-per-sec",
488           "Reserved MOOV bytes per second, per track",
489           "Multiplier for converting reserved-max-duration into bytes of header to reserve, per second, per track",
490           0, 10000, DEFAULT_RESERVED_BYTES_PER_SEC_PER_TRAK,
491           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
492   g_object_class_install_property (gobject_class, PROP_INTERLEAVE_BYTES,
493       g_param_spec_uint64 ("interleave-bytes", "Interleave (bytes)",
494           "Interleave between streams in bytes",
495           0, G_MAXUINT64, DEFAULT_INTERLEAVE_BYTES,
496           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
497   g_object_class_install_property (gobject_class, PROP_INTERLEAVE_TIME,
498       g_param_spec_uint64 ("interleave-time", "Interleave (time)",
499           "Interleave between streams in nanoseconds",
500           0, G_MAXUINT64, DEFAULT_INTERLEAVE_TIME,
501           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
502   g_object_class_install_property (gobject_class, PROP_MAX_RAW_AUDIO_DRIFT,
503       g_param_spec_uint64 ("max-raw-audio-drift", "Max Raw Audio Drift",
504           "Maximum allowed drift of raw audio samples vs. timestamps in nanoseconds",
505           0, G_MAXUINT64, DEFAULT_MAX_RAW_AUDIO_DRIFT,
506           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
507
508   gstelement_class->request_new_pad =
509       GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
510   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qt_mux_change_state);
511   gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_release_pad);
512 }
513
514 static void
515 gst_qt_mux_pad_reset (GstQTPad * qtpad)
516 {
517   qtpad->fourcc = 0;
518   qtpad->is_out_of_order = FALSE;
519   qtpad->sample_size = 0;
520   qtpad->sync = FALSE;
521   qtpad->last_dts = 0;
522   qtpad->sample_offset = 0;
523   qtpad->dts_adjustment = GST_CLOCK_TIME_NONE;
524   qtpad->first_ts = GST_CLOCK_TIME_NONE;
525   qtpad->first_dts = GST_CLOCK_TIME_NONE;
526   qtpad->prepare_buf_func = NULL;
527   qtpad->create_empty_buffer = NULL;
528   qtpad->avg_bitrate = 0;
529   qtpad->max_bitrate = 0;
530   qtpad->total_duration = 0;
531   qtpad->total_bytes = 0;
532   qtpad->sparse = FALSE;
533
534   gst_buffer_replace (&qtpad->last_buf, NULL);
535
536   if (qtpad->tags) {
537     gst_tag_list_unref (qtpad->tags);
538     qtpad->tags = NULL;
539   }
540
541   /* reference owned elsewhere */
542   qtpad->trak = NULL;
543   qtpad->tc_trak = NULL;
544
545   if (qtpad->traf) {
546     atom_traf_free (qtpad->traf);
547     qtpad->traf = NULL;
548   }
549   atom_array_clear (&qtpad->fragment_buffers);
550
551   /* reference owned elsewhere */
552   qtpad->tfra = NULL;
553
554   qtpad->first_pts = GST_CLOCK_TIME_NONE;
555   qtpad->tc_pos = -1;
556   if (qtpad->first_tc)
557     gst_video_time_code_free (qtpad->first_tc);
558   qtpad->first_tc = NULL;
559 }
560
561 /*
562  * Takes GstQTMux back to its initial state
563  */
564 static void
565 gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
566 {
567   GSList *walk;
568
569   qtmux->state = GST_QT_MUX_STATE_NONE;
570   qtmux->header_size = 0;
571   qtmux->mdat_size = 0;
572   qtmux->moov_pos = 0;
573   qtmux->mdat_pos = 0;
574   qtmux->longest_chunk = GST_CLOCK_TIME_NONE;
575   qtmux->fragment_sequence = 0;
576
577   if (qtmux->ftyp) {
578     atom_ftyp_free (qtmux->ftyp);
579     qtmux->ftyp = NULL;
580   }
581   if (qtmux->moov) {
582     atom_moov_free (qtmux->moov);
583     qtmux->moov = NULL;
584   }
585   if (qtmux->mfra) {
586     atom_mfra_free (qtmux->mfra);
587     qtmux->mfra = NULL;
588   }
589   if (qtmux->fast_start_file) {
590     fclose (qtmux->fast_start_file);
591     g_remove (qtmux->fast_start_file_path);
592     qtmux->fast_start_file = NULL;
593   }
594   if (qtmux->moov_recov_file) {
595     fclose (qtmux->moov_recov_file);
596     qtmux->moov_recov_file = NULL;
597   }
598   for (walk = qtmux->extra_atoms; walk; walk = g_slist_next (walk)) {
599     AtomInfo *ainfo = (AtomInfo *) walk->data;
600     ainfo->free_func (ainfo->atom);
601     g_free (ainfo);
602   }
603   g_slist_free (qtmux->extra_atoms);
604   qtmux->extra_atoms = NULL;
605
606   GST_OBJECT_LOCK (qtmux);
607   gst_tag_setter_reset_tags (GST_TAG_SETTER (qtmux));
608   GST_OBJECT_UNLOCK (qtmux);
609
610   /* reset pad data */
611   for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
612     GstQTPad *qtpad = (GstQTPad *) walk->data;
613     gst_qt_mux_pad_reset (qtpad);
614
615     /* hm, moov_free above yanked the traks away from us,
616      * so do not free, but do clear */
617     qtpad->trak = NULL;
618   }
619
620   if (alloc) {
621     qtmux->moov = atom_moov_new (qtmux->context);
622     /* ensure all is as nice and fresh as request_new_pad would provide it */
623     for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
624       GstQTPad *qtpad = (GstQTPad *) walk->data;
625
626       qtpad->trak = atom_trak_new (qtmux->context);
627       atom_moov_add_trak (qtmux->moov, qtpad->trak);
628     }
629   }
630
631   qtmux->current_pad = NULL;
632   qtmux->current_chunk_size = 0;
633   qtmux->current_chunk_duration = 0;
634   qtmux->current_chunk_offset = -1;
635
636   qtmux->reserved_moov_size = 0;
637   qtmux->last_moov_update = GST_CLOCK_TIME_NONE;
638   qtmux->muxed_since_last_update = 0;
639   qtmux->reserved_duration_remaining = GST_CLOCK_TIME_NONE;
640 }
641
642 static void
643 gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
644 {
645   GstElementClass *klass = GST_ELEMENT_CLASS (qtmux_klass);
646   GstPadTemplate *templ;
647
648   templ = gst_element_class_get_pad_template (klass, "src");
649   qtmux->srcpad = gst_pad_new_from_template (templ, "src");
650   gst_pad_use_fixed_caps (qtmux->srcpad);
651   gst_element_add_pad (GST_ELEMENT (qtmux), qtmux->srcpad);
652
653   qtmux->sinkpads = NULL;
654   qtmux->collect = gst_collect_pads_new ();
655   gst_collect_pads_set_event_function (qtmux->collect,
656       GST_DEBUG_FUNCPTR (gst_qt_mux_sink_event), qtmux);
657   gst_collect_pads_set_clip_function (qtmux->collect,
658       GST_DEBUG_FUNCPTR (gst_collect_pads_clip_running_time), qtmux);
659   gst_collect_pads_set_function (qtmux->collect,
660       GST_DEBUG_FUNCPTR (gst_qt_mux_collected), qtmux);
661
662   /* properties set to default upon construction */
663
664   qtmux->reserved_max_duration = DEFAULT_RESERVED_MAX_DURATION;
665   qtmux->reserved_moov_update_period = DEFAULT_RESERVED_MOOV_UPDATE_PERIOD;
666   qtmux->reserved_bytes_per_sec_per_trak =
667       DEFAULT_RESERVED_BYTES_PER_SEC_PER_TRAK;
668   qtmux->interleave_bytes = DEFAULT_INTERLEAVE_BYTES;
669   qtmux->interleave_time = DEFAULT_INTERLEAVE_TIME;
670   qtmux->max_raw_audio_drift = DEFAULT_MAX_RAW_AUDIO_DRIFT;
671
672   /* always need this */
673   qtmux->context =
674       atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format));
675
676   /* internals to initial state */
677   gst_qt_mux_reset (qtmux, TRUE);
678 }
679
680
681 static void
682 gst_qt_mux_finalize (GObject * object)
683 {
684   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
685
686   gst_qt_mux_reset (qtmux, FALSE);
687
688   g_free (qtmux->fast_start_file_path);
689   g_free (qtmux->moov_recov_file_path);
690
691   atoms_context_free (qtmux->context);
692   gst_object_unref (qtmux->collect);
693
694   g_slist_free (qtmux->sinkpads);
695
696   G_OBJECT_CLASS (parent_class)->finalize (object);
697 }
698
699 static GstBuffer *
700 gst_qt_mux_prepare_jpc_buffer (GstQTPad * qtpad, GstBuffer * buf,
701     GstQTMux * qtmux)
702 {
703   GstBuffer *newbuf;
704   GstMapInfo map;
705   gsize size;
706
707   GST_LOG_OBJECT (qtmux, "Preparing jpc buffer");
708
709   if (buf == NULL)
710     return NULL;
711
712   size = gst_buffer_get_size (buf);
713   newbuf = gst_buffer_new_and_alloc (size + 8);
714   gst_buffer_copy_into (newbuf, buf, GST_BUFFER_COPY_ALL, 8, size);
715
716   gst_buffer_map (newbuf, &map, GST_MAP_WRITE);
717   GST_WRITE_UINT32_BE (map.data, map.size);
718   GST_WRITE_UINT32_LE (map.data + 4, FOURCC_jp2c);
719
720   gst_buffer_unmap (buf, &map);
721   gst_buffer_unref (buf);
722
723   return newbuf;
724 }
725
726 static GstBuffer *
727 gst_qt_mux_prepare_tx3g_buffer (GstQTPad * qtpad, GstBuffer * buf,
728     GstQTMux * qtmux)
729 {
730   GstBuffer *newbuf;
731   GstMapInfo frommap;
732   GstMapInfo tomap;
733   gsize size;
734   const guint8 *dataend;
735
736   GST_LOG_OBJECT (qtmux, "Preparing tx3g buffer %" GST_PTR_FORMAT, buf);
737
738   if (buf == NULL)
739     return NULL;
740
741   gst_buffer_map (buf, &frommap, GST_MAP_READ);
742
743   dataend = memchr (frommap.data, 0, frommap.size);
744   size = dataend ? dataend - frommap.data : frommap.size;
745   newbuf = gst_buffer_new_and_alloc (size + 2);
746
747   gst_buffer_map (newbuf, &tomap, GST_MAP_WRITE);
748
749   GST_WRITE_UINT16_BE (tomap.data, size);
750   memcpy (tomap.data + 2, frommap.data, size);
751
752   gst_buffer_unmap (newbuf, &tomap);
753   gst_buffer_unmap (buf, &frommap);
754
755   gst_buffer_copy_into (newbuf, buf, GST_BUFFER_COPY_METADATA, 0, size);
756
757   /* gst_buffer_copy_into is trying to be too clever and
758    * won't copy duration when size is different */
759   GST_BUFFER_DURATION (newbuf) = GST_BUFFER_DURATION (buf);
760
761   gst_buffer_unref (buf);
762
763   return newbuf;
764 }
765
766 static void
767 gst_qt_mux_pad_add_ac3_extension (GstQTMux * qtmux, GstQTPad * qtpad,
768     guint8 fscod, guint8 frmsizcod, guint8 bsid, guint8 bsmod, guint8 acmod,
769     guint8 lfe_on)
770 {
771   AtomInfo *ext;
772
773   g_return_if_fail (qtpad->trak_ste);
774
775   ext = build_ac3_extension (fscod, bsid, bsmod, acmod, lfe_on, frmsizcod >> 1);        /* bitrate_code is inside frmsizcod */
776
777   sample_table_entry_add_ext_atom (qtpad->trak_ste, ext);
778 }
779
780 static GstBuffer *
781 gst_qt_mux_prepare_parse_ac3_frame (GstQTPad * qtpad, GstBuffer * buf,
782     GstQTMux * qtmux)
783 {
784   GstMapInfo map;
785   GstByteReader reader;
786   guint off;
787
788   if (!gst_buffer_map (buf, &map, GST_MAP_READ)) {
789     GST_WARNING_OBJECT (qtpad->collect.pad, "Failed to map buffer");
790     return buf;
791   }
792
793   if (G_UNLIKELY (map.size < 8))
794     goto done;
795
796   gst_byte_reader_init (&reader, map.data, map.size);
797   off = gst_byte_reader_masked_scan_uint32 (&reader, 0xffff0000, 0x0b770000,
798       0, map.size);
799
800   if (off != -1) {
801     GstBitReader bits;
802     guint8 fscod, frmsizcod, bsid, bsmod, acmod, lfe_on;
803
804     GST_DEBUG_OBJECT (qtpad->collect.pad, "Found ac3 sync point at offset: %u",
805         off);
806
807     gst_bit_reader_init (&bits, map.data, map.size);
808
809     /* off + sync + crc */
810     gst_bit_reader_skip_unchecked (&bits, off * 8 + 16 + 16);
811
812     fscod = gst_bit_reader_get_bits_uint8_unchecked (&bits, 2);
813     frmsizcod = gst_bit_reader_get_bits_uint8_unchecked (&bits, 6);
814     bsid = gst_bit_reader_get_bits_uint8_unchecked (&bits, 5);
815     bsmod = gst_bit_reader_get_bits_uint8_unchecked (&bits, 3);
816     acmod = gst_bit_reader_get_bits_uint8_unchecked (&bits, 3);
817
818     if ((acmod & 0x1) && (acmod != 0x1))        /* 3 front channels */
819       gst_bit_reader_skip_unchecked (&bits, 2);
820     if ((acmod & 0x4))          /* if a surround channel exists */
821       gst_bit_reader_skip_unchecked (&bits, 2);
822     if (acmod == 0x2)           /* if in 2/0 mode */
823       gst_bit_reader_skip_unchecked (&bits, 2);
824
825     lfe_on = gst_bit_reader_get_bits_uint8_unchecked (&bits, 1);
826
827     gst_qt_mux_pad_add_ac3_extension (qtmux, qtpad, fscod, frmsizcod, bsid,
828         bsmod, acmod, lfe_on);
829
830     /* AC-3 spec says that those values should be constant for the
831      * whole stream when muxed in mp4. We trust the input follows it */
832     GST_DEBUG_OBJECT (qtpad->collect.pad, "Data parsed, removing "
833         "prepare buffer function");
834     qtpad->prepare_buf_func = NULL;
835   }
836
837 done:
838   gst_buffer_unmap (buf, &map);
839   return buf;
840 }
841
842 static GstBuffer *
843 gst_qt_mux_create_empty_tx3g_buffer (GstQTPad * qtpad, gint64 duration)
844 {
845   guint8 *data;
846
847   data = g_malloc (2);
848   GST_WRITE_UINT16_BE (data, 0);
849
850   return gst_buffer_new_wrapped (data, 2);
851 }
852
853 static void
854 gst_qt_mux_add_mp4_tag (GstQTMux * qtmux, const GstTagList * list,
855     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
856 {
857   switch (gst_tag_get_type (tag)) {
858       /* strings */
859     case G_TYPE_STRING:
860     {
861       gchar *str = NULL;
862
863       if (!gst_tag_list_get_string (list, tag, &str) || !str)
864         break;
865       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
866           GST_FOURCC_ARGS (fourcc), str);
867       atom_udta_add_str_tag (udta, fourcc, str);
868       g_free (str);
869       break;
870     }
871       /* double */
872     case G_TYPE_DOUBLE:
873     {
874       gdouble value;
875
876       if (!gst_tag_list_get_double (list, tag, &value))
877         break;
878       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
879           GST_FOURCC_ARGS (fourcc), (gint) value);
880       atom_udta_add_uint_tag (udta, fourcc, 21, (gint) value);
881       break;
882     }
883     case G_TYPE_UINT:
884     {
885       guint value = 0;
886       if (tag2) {
887         /* paired unsigned integers */
888         guint count = 0;
889         gboolean got_tag;
890
891         got_tag = gst_tag_list_get_uint (list, tag, &value);
892         got_tag = gst_tag_list_get_uint (list, tag2, &count) || got_tag;
893         if (!got_tag)
894           break;
895         GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u",
896             GST_FOURCC_ARGS (fourcc), value, count);
897         atom_udta_add_uint_tag (udta, fourcc, 0,
898             value << 16 | (count & 0xFFFF));
899       } else {
900         /* unpaired unsigned integers */
901         if (!gst_tag_list_get_uint (list, tag, &value))
902           break;
903         GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
904             GST_FOURCC_ARGS (fourcc), value);
905         atom_udta_add_uint_tag (udta, fourcc, 1, value);
906       }
907       break;
908     }
909     default:
910       g_assert_not_reached ();
911       break;
912   }
913 }
914
915 static void
916 gst_qt_mux_add_mp4_date (GstQTMux * qtmux, const GstTagList * list,
917     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
918 {
919   GDate *date = NULL;
920   GDateYear year;
921   GDateMonth month;
922   GDateDay day;
923   gchar *str;
924
925   g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_DATE);
926
927   if (!gst_tag_list_get_date (list, tag, &date) || !date)
928     return;
929
930   year = g_date_get_year (date);
931   month = g_date_get_month (date);
932   day = g_date_get_day (date);
933
934   g_date_free (date);
935
936   if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH &&
937       day == G_DATE_BAD_DAY) {
938     GST_WARNING_OBJECT (qtmux, "invalid date in tag");
939     return;
940   }
941
942   str = g_strdup_printf ("%u-%u-%u", year, month, day);
943   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
944       GST_FOURCC_ARGS (fourcc), str);
945   atom_udta_add_str_tag (udta, fourcc, str);
946   g_free (str);
947 }
948
949 static void
950 gst_qt_mux_add_mp4_cover (GstQTMux * qtmux, const GstTagList * list,
951     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
952 {
953   GValue value = { 0, };
954   GstBuffer *buf;
955   GstSample *sample;
956   GstCaps *caps;
957   GstStructure *structure;
958   gint flags = 0;
959   GstMapInfo map;
960
961   g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_SAMPLE);
962
963   if (!gst_tag_list_copy_value (&value, list, tag))
964     return;
965
966   sample = gst_value_get_sample (&value);
967
968   if (!sample)
969     goto done;
970
971   buf = gst_sample_get_buffer (sample);
972   if (!buf)
973     goto done;
974
975   caps = gst_sample_get_caps (sample);
976   if (!caps) {
977     GST_WARNING_OBJECT (qtmux, "preview image without caps");
978     goto done;
979   }
980
981   GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);
982
983   structure = gst_caps_get_structure (caps, 0);
984   if (gst_structure_has_name (structure, "image/jpeg"))
985     flags = 13;
986   else if (gst_structure_has_name (structure, "image/png"))
987     flags = 14;
988
989   if (!flags) {
990     GST_WARNING_OBJECT (qtmux, "preview image format not supported");
991     goto done;
992   }
993
994   gst_buffer_map (buf, &map, GST_MAP_READ);
995   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT
996       " -> image size %" G_GSIZE_FORMAT "", GST_FOURCC_ARGS (fourcc), map.size);
997   atom_udta_add_tag (udta, fourcc, flags, map.data, map.size);
998   gst_buffer_unmap (buf, &map);
999 done:
1000   g_value_unset (&value);
1001 }
1002
1003 static void
1004 gst_qt_mux_add_3gp_str (GstQTMux * qtmux, const GstTagList * list,
1005     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
1006 {
1007   gchar *str = NULL;
1008   guint number;
1009
1010   g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_STRING);
1011   g_return_if_fail (!tag2 || gst_tag_get_type (tag2) == G_TYPE_UINT);
1012
1013   if (!gst_tag_list_get_string (list, tag, &str) || !str)
1014     return;
1015
1016   if (tag2)
1017     if (!gst_tag_list_get_uint (list, tag2, &number))
1018       tag2 = NULL;
1019
1020   if (!tag2) {
1021     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
1022         GST_FOURCC_ARGS (fourcc), str);
1023     atom_udta_add_3gp_str_tag (udta, fourcc, str);
1024   } else {
1025     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s/%d",
1026         GST_FOURCC_ARGS (fourcc), str, number);
1027     atom_udta_add_3gp_str_int_tag (udta, fourcc, str, number);
1028   }
1029
1030   g_free (str);
1031 }
1032
1033 static void
1034 gst_qt_mux_add_3gp_date (GstQTMux * qtmux, const GstTagList * list,
1035     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
1036 {
1037   GDate *date = NULL;
1038   GDateYear year;
1039
1040   g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_DATE);
1041
1042   if (!gst_tag_list_get_date (list, tag, &date) || !date)
1043     return;
1044
1045   year = g_date_get_year (date);
1046   g_date_free (date);
1047
1048   if (year == G_DATE_BAD_YEAR) {
1049     GST_WARNING_OBJECT (qtmux, "invalid date in tag");
1050     return;
1051   }
1052
1053   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %d",
1054       GST_FOURCC_ARGS (fourcc), year);
1055   atom_udta_add_3gp_uint_tag (udta, fourcc, year);
1056 }
1057
1058 static void
1059 gst_qt_mux_add_3gp_location (GstQTMux * qtmux, const GstTagList * list,
1060     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
1061 {
1062   gdouble latitude = -360, longitude = -360, altitude = 0;
1063   gchar *location = NULL;
1064   guint8 *data, *ddata;
1065   gint size = 0, len = 0;
1066   gboolean ret = FALSE;
1067
1068   g_return_if_fail (strcmp (tag, GST_TAG_GEO_LOCATION_NAME) == 0);
1069
1070   ret = gst_tag_list_get_string (list, tag, &location);
1071   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LONGITUDE,
1072       &longitude);
1073   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LATITUDE,
1074       &latitude);
1075   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_ELEVATION,
1076       &altitude);
1077
1078   if (!ret)
1079     return;
1080
1081   if (location)
1082     len = strlen (location);
1083   size += len + 1 + 2;
1084
1085   /* role + (long, lat, alt) + body + notes */
1086   size += 1 + 3 * 4 + 1 + 1;
1087
1088   data = ddata = g_malloc (size);
1089
1090   /* language tag */
1091   GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
1092   /* location */
1093   if (location)
1094     memcpy (data + 2, location, len);
1095   GST_WRITE_UINT8 (data + 2 + len, 0);
1096   data += len + 1 + 2;
1097   /* role */
1098   GST_WRITE_UINT8 (data, 0);
1099   /* long, lat, alt */
1100 #define QT_WRITE_SFP32(data, fp) GST_WRITE_UINT32_BE(data, (guint32) ((gint) (fp * 65536.0)))
1101   QT_WRITE_SFP32 (data + 1, longitude);
1102   QT_WRITE_SFP32 (data + 5, latitude);
1103   QT_WRITE_SFP32 (data + 9, altitude);
1104   /* neither astronomical body nor notes */
1105   GST_WRITE_UINT16_BE (data + 13, 0);
1106
1107   GST_DEBUG_OBJECT (qtmux, "Adding tag 'loci'");
1108   atom_udta_add_3gp_tag (udta, fourcc, ddata, size);
1109   g_free (ddata);
1110 }
1111
1112 static void
1113 gst_qt_mux_add_3gp_keywords (GstQTMux * qtmux, const GstTagList * list,
1114     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
1115 {
1116   gchar *keywords = NULL;
1117   guint8 *data, *ddata;
1118   gint size = 0, i;
1119   gchar **kwds;
1120
1121   g_return_if_fail (strcmp (tag, GST_TAG_KEYWORDS) == 0);
1122
1123   if (!gst_tag_list_get_string (list, tag, &keywords) || !keywords)
1124     return;
1125
1126   kwds = g_strsplit (keywords, ",", 0);
1127   g_free (keywords);
1128
1129   size = 0;
1130   for (i = 0; kwds[i]; i++) {
1131     /* size byte + null-terminator */
1132     size += strlen (kwds[i]) + 1 + 1;
1133   }
1134
1135   /* language tag + count + keywords */
1136   size += 2 + 1;
1137
1138   data = ddata = g_malloc (size);
1139
1140   /* language tag */
1141   GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
1142   /* count */
1143   GST_WRITE_UINT8 (data + 2, i);
1144   data += 3;
1145   /* keywords */
1146   for (i = 0; kwds[i]; ++i) {
1147     gint len = strlen (kwds[i]);
1148
1149     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
1150         GST_FOURCC_ARGS (fourcc), kwds[i]);
1151     /* size */
1152     GST_WRITE_UINT8 (data, len + 1);
1153     memcpy (data + 1, kwds[i], len + 1);
1154     data += len + 2;
1155   }
1156
1157   g_strfreev (kwds);
1158
1159   atom_udta_add_3gp_tag (udta, fourcc, ddata, size);
1160   g_free (ddata);
1161 }
1162
1163 static gboolean
1164 gst_qt_mux_parse_classification_string (GstQTMux * qtmux, const gchar * input,
1165     guint32 * p_fourcc, guint16 * p_table, gchar ** p_content)
1166 {
1167   guint32 fourcc;
1168   gint table;
1169   gint size;
1170   const gchar *data;
1171
1172   data = input;
1173   size = strlen (input);
1174
1175   if (size < 4 + 3 + 1 + 1 + 1) {
1176     /* at least the minimum xxxx://y/z */
1177     GST_WARNING_OBJECT (qtmux, "Classification tag input (%s) too short, "
1178         "ignoring", input);
1179     return FALSE;
1180   }
1181
1182   /* read the fourcc */
1183   memcpy (&fourcc, data, 4);
1184   size -= 4;
1185   data += 4;
1186
1187   if (strncmp (data, "://", 3) != 0) {
1188     goto mismatch;
1189   }
1190   data += 3;
1191   size -= 3;
1192
1193   /* read the table number */
1194   if (sscanf (data, "%d", &table) != 1) {
1195     goto mismatch;
1196   }
1197   if (table < 0) {
1198     GST_WARNING_OBJECT (qtmux, "Invalid table number in classification tag (%d)"
1199         ", table numbers should be positive, ignoring tag", table);
1200     return FALSE;
1201   }
1202
1203   /* find the next / */
1204   while (size > 0 && data[0] != '/') {
1205     data += 1;
1206     size -= 1;
1207   }
1208   if (size == 0) {
1209     goto mismatch;
1210   }
1211   g_assert (data[0] == '/');
1212
1213   /* skip the '/' */
1214   data += 1;
1215   size -= 1;
1216   if (size == 0) {
1217     goto mismatch;
1218   }
1219
1220   /* read up the rest of the string */
1221   *p_content = g_strdup (data);
1222   *p_table = (guint16) table;
1223   *p_fourcc = fourcc;
1224   return TRUE;
1225
1226 mismatch:
1227   {
1228     GST_WARNING_OBJECT (qtmux, "Ignoring classification tag as "
1229         "input (%s) didn't match the expected entitycode://table/content",
1230         input);
1231     return FALSE;
1232   }
1233 }
1234
1235 static void
1236 gst_qt_mux_add_3gp_classification (GstQTMux * qtmux, const GstTagList * list,
1237     AtomUDTA * udta, const char *tag, const char *tag2, guint32 fourcc)
1238 {
1239   gchar *clsf_data = NULL;
1240   gint size = 0;
1241   guint32 entity = 0;
1242   guint16 table = 0;
1243   gchar *content = NULL;
1244   guint8 *data;
1245
1246   g_return_if_fail (strcmp (tag, GST_TAG_3GP_CLASSIFICATION) == 0);
1247
1248   if (!gst_tag_list_get_string (list, tag, &clsf_data) || !clsf_data)
1249     return;
1250
1251   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
1252       GST_FOURCC_ARGS (fourcc), clsf_data);
1253
1254   /* parse the string, format is:
1255    * entityfourcc://table/content
1256    */
1257   gst_qt_mux_parse_classification_string (qtmux, clsf_data, &entity, &table,
1258       &content);
1259   g_free (clsf_data);
1260   /* +1 for the \0 */
1261   size = strlen (content) + 1;
1262
1263   /* now we have everything, build the atom
1264    * atom description is at 3GPP TS 26.244 V8.2.0 (2009-09) */
1265   data = g_malloc (4 + 2 + 2 + size);
1266   GST_WRITE_UINT32_LE (data, entity);
1267   GST_WRITE_UINT16_BE (data + 4, (guint16) table);
1268   GST_WRITE_UINT16_BE (data + 6, 0);
1269   memcpy (data + 8, content, size);
1270   g_free (content);
1271
1272   atom_udta_add_3gp_tag (udta, fourcc, data, 4 + 2 + 2 + size);
1273   g_free (data);
1274 }
1275
1276 typedef void (*GstQTMuxAddUdtaTagFunc) (GstQTMux * mux,
1277     const GstTagList * list, AtomUDTA * udta, const char *tag,
1278     const char *tag2, guint32 fourcc);
1279
1280 /*
1281  * Struct to record mappings from gstreamer tags to fourcc codes
1282  */
1283 typedef struct _GstTagToFourcc
1284 {
1285   guint32 fourcc;
1286   const gchar *gsttag;
1287   const gchar *gsttag2;
1288   const GstQTMuxAddUdtaTagFunc func;
1289 } GstTagToFourcc;
1290
1291 /* tag list tags to fourcc matching */
1292 static const GstTagToFourcc tag_matches_mp4[] = {
1293   {FOURCC__alb, GST_TAG_ALBUM, NULL, gst_qt_mux_add_mp4_tag},
1294   {FOURCC_soal, GST_TAG_ALBUM_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1295   {FOURCC__ART, GST_TAG_ARTIST, NULL, gst_qt_mux_add_mp4_tag},
1296   {FOURCC_soar, GST_TAG_ARTIST_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1297   {FOURCC_aART, GST_TAG_ALBUM_ARTIST, NULL, gst_qt_mux_add_mp4_tag},
1298   {FOURCC_soaa, GST_TAG_ALBUM_ARTIST_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1299   {FOURCC__swr, GST_TAG_APPLICATION_NAME, NULL, gst_qt_mux_add_mp4_tag},
1300   {FOURCC__cmt, GST_TAG_COMMENT, NULL, gst_qt_mux_add_mp4_tag},
1301   {FOURCC__wrt, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_mp4_tag},
1302   {FOURCC_soco, GST_TAG_COMPOSER_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1303   {FOURCC_tvsh, GST_TAG_SHOW_NAME, NULL, gst_qt_mux_add_mp4_tag},
1304   {FOURCC_sosn, GST_TAG_SHOW_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1305   {FOURCC_tvsn, GST_TAG_SHOW_SEASON_NUMBER, NULL, gst_qt_mux_add_mp4_tag},
1306   {FOURCC_tves, GST_TAG_SHOW_EPISODE_NUMBER, NULL, gst_qt_mux_add_mp4_tag},
1307   {FOURCC__gen, GST_TAG_GENRE, NULL, gst_qt_mux_add_mp4_tag},
1308   {FOURCC__nam, GST_TAG_TITLE, NULL, gst_qt_mux_add_mp4_tag},
1309   {FOURCC_sonm, GST_TAG_TITLE_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
1310   {FOURCC_perf, GST_TAG_PERFORMER, NULL, gst_qt_mux_add_mp4_tag},
1311   {FOURCC__grp, GST_TAG_GROUPING, NULL, gst_qt_mux_add_mp4_tag},
1312   {FOURCC__des, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_mp4_tag},
1313   {FOURCC__lyr, GST_TAG_LYRICS, NULL, gst_qt_mux_add_mp4_tag},
1314   {FOURCC__too, GST_TAG_ENCODER, NULL, gst_qt_mux_add_mp4_tag},
1315   {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_mp4_tag},
1316   {FOURCC_keyw, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_mp4_tag},
1317   {FOURCC__day, GST_TAG_DATE, NULL, gst_qt_mux_add_mp4_date},
1318   {FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE, NULL, gst_qt_mux_add_mp4_tag},
1319   {FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT,
1320       gst_qt_mux_add_mp4_tag},
1321   {FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT,
1322       gst_qt_mux_add_mp4_tag},
1323   {FOURCC_covr, GST_TAG_PREVIEW_IMAGE, NULL, gst_qt_mux_add_mp4_cover},
1324   {FOURCC_covr, GST_TAG_IMAGE, NULL, gst_qt_mux_add_mp4_cover},
1325   {0, NULL,}
1326 };
1327
1328 static const GstTagToFourcc tag_matches_3gp[] = {
1329   {FOURCC_titl, GST_TAG_TITLE, NULL, gst_qt_mux_add_3gp_str},
1330   {FOURCC_dscp, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_3gp_str},
1331   {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_3gp_str},
1332   {FOURCC_perf, GST_TAG_ARTIST, NULL, gst_qt_mux_add_3gp_str},
1333   {FOURCC_auth, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_3gp_str},
1334   {FOURCC_gnre, GST_TAG_GENRE, NULL, gst_qt_mux_add_3gp_str},
1335   {FOURCC_kywd, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_3gp_keywords},
1336   {FOURCC_yrrc, GST_TAG_DATE, NULL, gst_qt_mux_add_3gp_date},
1337   {FOURCC_albm, GST_TAG_ALBUM, GST_TAG_TRACK_NUMBER, gst_qt_mux_add_3gp_str},
1338   {FOURCC_loci, GST_TAG_GEO_LOCATION_NAME, NULL, gst_qt_mux_add_3gp_location},
1339   {FOURCC_clsf, GST_TAG_3GP_CLASSIFICATION, NULL,
1340       gst_qt_mux_add_3gp_classification},
1341   {0, NULL,}
1342 };
1343
1344 /* qtdemux produces these for atoms it cannot parse */
1345 #define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag"
1346
1347 static void
1348 gst_qt_mux_add_xmp_tags (GstQTMux * qtmux, const GstTagList * list)
1349 {
1350   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1351   GstBuffer *xmp = NULL;
1352
1353   /* adobe specs only have 'quicktime' and 'mp4',
1354    * but I guess we can extrapolate to gpp.
1355    * Keep mj2 out for now as we don't add any tags for it yet.
1356    * If you have further info about xmp on these formats, please share */
1357   if (qtmux_klass->format == GST_QT_MUX_FORMAT_MJ2)
1358     return;
1359
1360   GST_DEBUG_OBJECT (qtmux, "Adding xmp tags");
1361
1362   if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT) {
1363     xmp = gst_tag_xmp_writer_tag_list_to_xmp_buffer (GST_TAG_XMP_WRITER (qtmux),
1364         list, TRUE);
1365     if (xmp)
1366       atom_udta_add_xmp_tags (&qtmux->moov->udta, xmp);
1367   } else {
1368     AtomInfo *ainfo;
1369     /* for isom/mp4, it is a top level uuid atom */
1370     xmp = gst_tag_xmp_writer_tag_list_to_xmp_buffer (GST_TAG_XMP_WRITER (qtmux),
1371         list, TRUE);
1372     if (xmp) {
1373       ainfo = build_uuid_xmp_atom (xmp);
1374       if (ainfo) {
1375         qtmux->extra_atoms = g_slist_prepend (qtmux->extra_atoms, ainfo);
1376       }
1377     }
1378   }
1379   if (xmp)
1380     gst_buffer_unref (xmp);
1381 }
1382
1383 static void
1384 gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list,
1385     AtomUDTA * udta)
1386 {
1387   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1388   guint32 fourcc;
1389   gint i;
1390   const gchar *tag, *tag2;
1391   const GstTagToFourcc *tag_matches;
1392
1393   switch (qtmux_klass->format) {
1394     case GST_QT_MUX_FORMAT_3GP:
1395       tag_matches = tag_matches_3gp;
1396       break;
1397     case GST_QT_MUX_FORMAT_MJ2:
1398       tag_matches = NULL;
1399       break;
1400     default:
1401       /* sort of iTunes style for mp4 and QT (?) */
1402       tag_matches = tag_matches_mp4;
1403       break;
1404   }
1405
1406   if (!tag_matches)
1407     return;
1408
1409   /* Clear existing tags so we don't add them over and over */
1410   atom_udta_clear_tags (udta);
1411
1412   for (i = 0; tag_matches[i].fourcc; i++) {
1413     fourcc = tag_matches[i].fourcc;
1414     tag = tag_matches[i].gsttag;
1415     tag2 = tag_matches[i].gsttag2;
1416
1417     g_assert (tag_matches[i].func);
1418     tag_matches[i].func (qtmux, list, udta, tag, tag2, fourcc);
1419   }
1420
1421   /* add unparsed blobs if present */
1422   if (gst_tag_exists (GST_QT_DEMUX_PRIVATE_TAG)) {
1423     guint num_tags;
1424
1425     num_tags = gst_tag_list_get_tag_size (list, GST_QT_DEMUX_PRIVATE_TAG);
1426     for (i = 0; i < num_tags; ++i) {
1427       GstSample *sample = NULL;
1428       GstBuffer *buf;
1429       const GstStructure *s;
1430
1431       if (!gst_tag_list_get_sample_index (list, GST_QT_DEMUX_PRIVATE_TAG, i,
1432               &sample))
1433         continue;
1434       buf = gst_sample_get_buffer (sample);
1435
1436       if (buf && (s = gst_sample_get_info (sample))) {
1437         const gchar *style = NULL;
1438         GstMapInfo map;
1439
1440         gst_buffer_map (buf, &map, GST_MAP_READ);
1441         GST_DEBUG_OBJECT (qtmux,
1442             "Found private tag %d/%d; size %" G_GSIZE_FORMAT ", info %"
1443             GST_PTR_FORMAT, i, num_tags, map.size, s);
1444         if (s && (style = gst_structure_get_string (s, "style"))) {
1445           /* try to prevent some style tag ending up into another variant
1446            * (todo: make into a list if more cases) */
1447           if ((strcmp (style, "itunes") == 0 &&
1448                   qtmux_klass->format == GST_QT_MUX_FORMAT_MP4) ||
1449               (strcmp (style, "iso") == 0 &&
1450                   qtmux_klass->format == GST_QT_MUX_FORMAT_3GP)) {
1451             GST_DEBUG_OBJECT (qtmux, "Adding private tag");
1452             atom_udta_add_blob_tag (udta, map.data, map.size);
1453           }
1454         }
1455         gst_buffer_unmap (buf, &map);
1456       }
1457       gst_sample_unref (sample);
1458     }
1459   }
1460
1461   return;
1462 }
1463
1464 /*
1465  * Gets the tagsetter iface taglist and puts the known tags
1466  * into the output stream
1467  */
1468 static void
1469 gst_qt_mux_setup_metadata (GstQTMux * qtmux)
1470 {
1471   const GstTagList *tags = NULL;
1472   GSList *walk;
1473
1474   GST_OBJECT_LOCK (qtmux);
1475   if (qtmux->tags_changed) {
1476     tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (qtmux));
1477     qtmux->tags_changed = FALSE;
1478   }
1479   GST_OBJECT_UNLOCK (qtmux);
1480
1481   GST_LOG_OBJECT (qtmux, "tags: %" GST_PTR_FORMAT, tags);
1482
1483   if (tags && !gst_tag_list_is_empty (tags)) {
1484     GstTagList *copy = gst_tag_list_copy (tags);
1485
1486     GST_DEBUG_OBJECT (qtmux, "Removing bogus tags");
1487     gst_tag_list_remove_tag (copy, GST_TAG_VIDEO_CODEC);
1488     gst_tag_list_remove_tag (copy, GST_TAG_AUDIO_CODEC);
1489     gst_tag_list_remove_tag (copy, GST_TAG_CONTAINER_FORMAT);
1490
1491     GST_DEBUG_OBJECT (qtmux, "Formatting tags");
1492     gst_qt_mux_add_metadata_tags (qtmux, copy, &qtmux->moov->udta);
1493     gst_qt_mux_add_xmp_tags (qtmux, copy);
1494     gst_tag_list_unref (copy);
1495   } else {
1496     GST_DEBUG_OBJECT (qtmux, "No new tags received");
1497   }
1498
1499   for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
1500     GstCollectData *cdata = (GstCollectData *) walk->data;
1501     GstQTPad *qpad = (GstQTPad *) cdata;
1502     GstPad *pad = qpad->collect.pad;
1503
1504     if (qpad->tags_changed && qpad->tags) {
1505       GST_DEBUG_OBJECT (pad, "Adding tags");
1506       gst_tag_list_remove_tag (qpad->tags, GST_TAG_CONTAINER_FORMAT);
1507       gst_qt_mux_add_metadata_tags (qtmux, qpad->tags, &qpad->trak->udta);
1508       qpad->tags_changed = FALSE;
1509       GST_DEBUG_OBJECT (pad, "Tags added");
1510     } else {
1511       GST_DEBUG_OBJECT (pad, "No new tags received");
1512     }
1513   }
1514 }
1515
1516 static inline GstBuffer *
1517 _gst_buffer_new_take_data (guint8 * data, guint size)
1518 {
1519   GstBuffer *buf;
1520
1521   buf = gst_buffer_new ();
1522   gst_buffer_append_memory (buf,
1523       gst_memory_new_wrapped (0, data, size, 0, size, data, g_free));
1524
1525   return buf;
1526 }
1527
1528 static GstFlowReturn
1529 gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset,
1530     gboolean mind_fast)
1531 {
1532   GstFlowReturn res;
1533   gsize size;
1534
1535   g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
1536
1537   size = gst_buffer_get_size (buf);
1538   GST_LOG_OBJECT (qtmux, "sending buffer size %" G_GSIZE_FORMAT, size);
1539
1540   if (mind_fast && qtmux->fast_start_file) {
1541     GstMapInfo map;
1542     gint ret;
1543
1544     GST_LOG_OBJECT (qtmux, "to temporary file");
1545     gst_buffer_map (buf, &map, GST_MAP_READ);
1546     ret = fwrite (map.data, sizeof (guint8), map.size, qtmux->fast_start_file);
1547     gst_buffer_unmap (buf, &map);
1548     gst_buffer_unref (buf);
1549     if (ret != size)
1550       goto write_error;
1551     else
1552       res = GST_FLOW_OK;
1553   } else {
1554     GST_LOG_OBJECT (qtmux, "downstream");
1555     res = gst_pad_push (qtmux->srcpad, buf);
1556   }
1557
1558   if (G_LIKELY (offset))
1559     *offset += size;
1560
1561   return res;
1562
1563   /* ERRORS */
1564 write_error:
1565   {
1566     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
1567         ("Failed to write to temporary file"), GST_ERROR_SYSTEM);
1568     return GST_FLOW_ERROR;
1569   }
1570 }
1571
1572 static gboolean
1573 gst_qt_mux_seek_to_beginning (FILE * f)
1574 {
1575 #ifdef HAVE_FSEEKO
1576   if (fseeko (f, (off_t) 0, SEEK_SET) != 0)
1577     return FALSE;
1578 #elif defined (G_OS_UNIX) || defined (G_OS_WIN32)
1579   if (lseek (fileno (f), (off_t) 0, SEEK_SET) == (off_t) - 1)
1580     return FALSE;
1581 #else
1582   if (fseek (f, (long) 0, SEEK_SET) != 0)
1583     return FALSE;
1584 #endif
1585   return TRUE;
1586 }
1587
1588 static GstFlowReturn
1589 gst_qt_mux_send_buffered_data (GstQTMux * qtmux, guint64 * offset)
1590 {
1591   GstFlowReturn ret = GST_FLOW_OK;
1592   GstBuffer *buf = NULL;
1593
1594   if (fflush (qtmux->fast_start_file))
1595     goto flush_failed;
1596
1597   if (!gst_qt_mux_seek_to_beginning (qtmux->fast_start_file))
1598     goto seek_failed;
1599
1600   /* hm, this could all take a really really long time,
1601    * but there may not be another way to get moov atom first
1602    * (somehow optimize copy?) */
1603   GST_DEBUG_OBJECT (qtmux, "Sending buffered data");
1604   while (ret == GST_FLOW_OK) {
1605     const int bufsize = 4096;
1606     GstMapInfo map;
1607     gsize size;
1608
1609     buf = gst_buffer_new_and_alloc (bufsize);
1610     gst_buffer_map (buf, &map, GST_MAP_WRITE);
1611     size = fread (map.data, sizeof (guint8), bufsize, qtmux->fast_start_file);
1612     if (size == 0) {
1613       gst_buffer_unmap (buf, &map);
1614       break;
1615     }
1616     GST_LOG_OBJECT (qtmux, "Pushing buffered buffer of size %d", (gint) size);
1617     gst_buffer_unmap (buf, &map);
1618     if (size != bufsize)
1619       gst_buffer_set_size (buf, size);
1620     ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
1621     buf = NULL;
1622   }
1623   if (buf)
1624     gst_buffer_unref (buf);
1625
1626   if (ftruncate (fileno (qtmux->fast_start_file), 0))
1627     goto seek_failed;
1628   if (!gst_qt_mux_seek_to_beginning (qtmux->fast_start_file))
1629     goto seek_failed;
1630
1631   return ret;
1632
1633   /* ERRORS */
1634 flush_failed:
1635   {
1636     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
1637         ("Failed to flush temporary file"), GST_ERROR_SYSTEM);
1638     ret = GST_FLOW_ERROR;
1639     goto fail;
1640   }
1641 seek_failed:
1642   {
1643     GST_ELEMENT_ERROR (qtmux, RESOURCE, SEEK,
1644         ("Failed to seek temporary file"), GST_ERROR_SYSTEM);
1645     ret = GST_FLOW_ERROR;
1646     goto fail;
1647   }
1648 fail:
1649   {
1650     /* clear descriptor so we don't remove temp file later on,
1651      * might be possible to recover */
1652     fclose (qtmux->fast_start_file);
1653     qtmux->fast_start_file = NULL;
1654     return ret;
1655   }
1656 }
1657
1658 /*
1659  * Sends the initial mdat atom fields (size fields and fourcc type),
1660  * the subsequent buffers are considered part of it's data.
1661  * As we can't predict the amount of data that we are going to place in mdat
1662  * we need to record the position of the size field in the stream so we can
1663  * seek back to it later and update when the streams have finished.
1664  */
1665 static GstFlowReturn
1666 gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size,
1667     gboolean extended, gboolean fsync_after)
1668 {
1669   GstBuffer *buf;
1670   GstMapInfo map;
1671
1672   GST_DEBUG_OBJECT (qtmux, "Sending mdat's atom header, "
1673       "size %" G_GUINT64_FORMAT, size);
1674
1675   /* if the qtmux state is EOS, really write the mdat, otherwise
1676    * allow size == 0 for a placeholder atom */
1677   if (qtmux->state == GST_QT_MUX_STATE_EOS || size > 0)
1678     size += 8;
1679
1680   if (extended) {
1681     gboolean large_file = (size > MDAT_LARGE_FILE_LIMIT);
1682     /* Always write 16-bytes, but put a free atom first
1683      * if the size is < 4GB. */
1684     buf = gst_buffer_new_and_alloc (16);
1685     gst_buffer_map (buf, &map, GST_MAP_WRITE);
1686
1687     if (large_file) {
1688       /* Write extended mdat header and large_size field */
1689       GST_WRITE_UINT32_BE (map.data, 1);
1690       GST_WRITE_UINT32_LE (map.data + 4, FOURCC_mdat);
1691       GST_WRITE_UINT64_BE (map.data + 8, size + 8);
1692     } else {
1693       /* Write an empty free atom, then standard 32-bit mdat */
1694       GST_WRITE_UINT32_BE (map.data, 8);
1695       GST_WRITE_UINT32_LE (map.data + 4, FOURCC_free);
1696       GST_WRITE_UINT32_BE (map.data + 8, size);
1697       GST_WRITE_UINT32_LE (map.data + 12, FOURCC_mdat);
1698     }
1699     gst_buffer_unmap (buf, &map);
1700   } else {
1701     buf = gst_buffer_new_and_alloc (8);
1702     gst_buffer_map (buf, &map, GST_MAP_WRITE);
1703
1704     /* Vanilla 32-bit mdat */
1705     GST_WRITE_UINT32_BE (map.data, size);
1706     GST_WRITE_UINT32_LE (map.data + 4, FOURCC_mdat);
1707     gst_buffer_unmap (buf, &map);
1708   }
1709
1710   GST_LOG_OBJECT (qtmux, "Pushing mdat header");
1711   if (fsync_after)
1712     GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_SYNC_AFTER);
1713
1714   return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
1715
1716 }
1717
1718 /*
1719  * We get the position of the mdat size field, seek back to it
1720  * and overwrite with the real value
1721  */
1722 static GstFlowReturn
1723 gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos,
1724     guint64 mdat_size, guint64 * offset, gboolean fsync_after)
1725 {
1726   GstSegment segment;
1727
1728   /* We must have recorded the mdat position for this to work */
1729   g_assert (mdat_pos != 0);
1730
1731   /* seek and rewrite the header */
1732   gst_segment_init (&segment, GST_FORMAT_BYTES);
1733   segment.start = mdat_pos;
1734   gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
1735
1736   return gst_qt_mux_send_mdat_header (qtmux, offset, mdat_size, TRUE,
1737       fsync_after);
1738 }
1739
1740 static GstFlowReturn
1741 gst_qt_mux_send_ftyp (GstQTMux * qtmux, guint64 * off)
1742 {
1743   GstBuffer *buf;
1744   guint64 size = 0, offset = 0;
1745   guint8 *data = NULL;
1746
1747   GST_DEBUG_OBJECT (qtmux, "Sending ftyp atom");
1748
1749   if (!atom_ftyp_copy_data (qtmux->ftyp, &data, &size, &offset))
1750     goto serialize_error;
1751
1752   buf = _gst_buffer_new_take_data (data, offset);
1753
1754   GST_LOG_OBJECT (qtmux, "Pushing ftyp");
1755   return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
1756
1757   /* ERRORS */
1758 serialize_error:
1759   {
1760     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1761         ("Failed to serialize ftyp"));
1762     return GST_FLOW_ERROR;
1763   }
1764 }
1765
1766 static void
1767 gst_qt_mux_prepare_ftyp (GstQTMux * qtmux, AtomFTYP ** p_ftyp,
1768     GstBuffer ** p_prefix)
1769 {
1770   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1771   guint32 major, version;
1772   GList *comp;
1773   GstBuffer *prefix = NULL;
1774   AtomFTYP *ftyp = NULL;
1775
1776   GST_DEBUG_OBJECT (qtmux, "Preparing ftyp and possible prefix atom");
1777
1778   /* init and send context and ftyp based on current property state */
1779   gst_qt_mux_map_format_to_header (qtmux_klass->format, &prefix, &major,
1780       &version, &comp, qtmux->moov, qtmux->longest_chunk,
1781       qtmux->fast_start_file != NULL);
1782   ftyp = atom_ftyp_new (qtmux->context, major, version, comp);
1783   if (comp)
1784     g_list_free (comp);
1785   if (prefix) {
1786     if (p_prefix)
1787       *p_prefix = prefix;
1788     else
1789       gst_buffer_unref (prefix);
1790   }
1791   *p_ftyp = ftyp;
1792 }
1793
1794 static GstFlowReturn
1795 gst_qt_mux_prepare_and_send_ftyp (GstQTMux * qtmux)
1796 {
1797   GstFlowReturn ret = GST_FLOW_OK;
1798   GstBuffer *prefix = NULL;
1799
1800   GST_DEBUG_OBJECT (qtmux, "Preparing to send ftyp atom");
1801
1802   /* init and send context and ftyp based on current property state */
1803   if (qtmux->ftyp) {
1804     atom_ftyp_free (qtmux->ftyp);
1805     qtmux->ftyp = NULL;
1806   }
1807   gst_qt_mux_prepare_ftyp (qtmux, &qtmux->ftyp, &prefix);
1808   if (prefix) {
1809     ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE);
1810     if (ret != GST_FLOW_OK)
1811       return ret;
1812   }
1813   return gst_qt_mux_send_ftyp (qtmux, &qtmux->header_size);
1814 }
1815
1816 static void
1817 gst_qt_mux_set_header_on_caps (GstQTMux * mux, GstBuffer * buf)
1818 {
1819   GstStructure *structure;
1820   GValue array = { 0 };
1821   GValue value = { 0 };
1822   GstCaps *caps, *tcaps;
1823
1824   tcaps = gst_pad_get_current_caps (mux->srcpad);
1825   caps = gst_caps_copy (tcaps);
1826   gst_caps_unref (tcaps);
1827
1828   structure = gst_caps_get_structure (caps, 0);
1829
1830   g_value_init (&array, GST_TYPE_ARRAY);
1831
1832   GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);
1833   g_value_init (&value, GST_TYPE_BUFFER);
1834   gst_value_take_buffer (&value, gst_buffer_ref (buf));
1835   gst_value_array_append_value (&array, &value);
1836   g_value_unset (&value);
1837
1838   gst_structure_set_value (structure, "streamheader", &array);
1839   g_value_unset (&array);
1840   gst_pad_set_caps (mux->srcpad, caps);
1841   gst_caps_unref (caps);
1842 }
1843
1844 /*
1845  * Write out a free space atom. The offset is adjusted by the full
1846  * size, but a smaller buffer is sent
1847  */
1848 static GstFlowReturn
1849 gst_qt_mux_send_free_atom (GstQTMux * qtmux, guint64 * off, guint32 size,
1850     gboolean fsync_after)
1851 {
1852   Atom *node_header;
1853   GstBuffer *buf;
1854   guint8 *data = NULL;
1855   guint64 offset = 0, bsize = 0;
1856   GstFlowReturn ret;
1857
1858   GST_DEBUG_OBJECT (qtmux, "Sending free atom header of size %u", size);
1859
1860   /* We can't make a free space atom smaller than the header */
1861   if (size < 8)
1862     goto too_small;
1863
1864   node_header = g_malloc0 (sizeof (Atom));
1865   node_header->type = FOURCC_free;
1866   node_header->size = size;
1867
1868   bsize = offset = 0;
1869   if (atom_copy_data (node_header, &data, &bsize, &offset) == 0)
1870     goto serialize_error;
1871
1872   buf = _gst_buffer_new_take_data (data, offset);
1873   g_free (node_header);
1874
1875   if (fsync_after)
1876     GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_SYNC_AFTER);
1877
1878   GST_LOG_OBJECT (qtmux, "Pushing free atom");
1879   ret = gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
1880
1881   if (off) {
1882     GstSegment segment;
1883
1884     *off += size - 8;
1885
1886     /* Make sure downstream position ends up at the end of this free box */
1887     gst_segment_init (&segment, GST_FORMAT_BYTES);
1888     segment.start = *off;
1889     gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
1890   }
1891
1892   return ret;
1893
1894   /* ERRORS */
1895 too_small:
1896   {
1897     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1898         ("Not enough free reserved space"));
1899     return GST_FLOW_ERROR;
1900   }
1901 serialize_error:
1902   {
1903     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1904         ("Failed to serialize mdat"));
1905     g_free (node_header);
1906     return GST_FLOW_ERROR;
1907   }
1908 }
1909
1910 static void
1911 gst_qt_mux_configure_moov (GstQTMux * qtmux)
1912 {
1913   gboolean fragmented = FALSE;
1914   guint32 timescale;
1915
1916   GST_OBJECT_LOCK (qtmux);
1917   timescale = qtmux->timescale;
1918   if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED ||
1919       qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE)
1920     fragmented = TRUE;
1921   GST_OBJECT_UNLOCK (qtmux);
1922
1923   /* inform lower layers of our property wishes, and determine duration.
1924    * Let moov take care of this using its list of traks;
1925    * so that released pads are also included */
1926   GST_DEBUG_OBJECT (qtmux, "Updating timescale to %" G_GUINT32_FORMAT,
1927       timescale);
1928   atom_moov_update_timescale (qtmux->moov, timescale);
1929   atom_moov_set_fragmented (qtmux->moov, fragmented);
1930
1931   atom_moov_update_duration (qtmux->moov);
1932 }
1933
1934 static GstFlowReturn
1935 gst_qt_mux_send_moov (GstQTMux * qtmux, guint64 * _offset,
1936     guint64 padded_moov_size, gboolean mind_fast, gboolean fsync_after)
1937 {
1938   guint64 offset = 0, size = 0;
1939   guint8 *data;
1940   GstBuffer *buf;
1941   GstFlowReturn ret = GST_FLOW_OK;
1942   GSList *walk;
1943   guint64 current_time = atoms_get_current_qt_time ();
1944
1945   /* update modification times */
1946   qtmux->moov->mvhd.time_info.modification_time = current_time;
1947   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
1948     GstCollectData *cdata = (GstCollectData *) walk->data;
1949     GstQTPad *qtpad = (GstQTPad *) cdata;
1950
1951     qtpad->trak->mdia.mdhd.time_info.modification_time = current_time;
1952     qtpad->trak->tkhd.modification_time = current_time;
1953   }
1954
1955   /* serialize moov */
1956   offset = size = 0;
1957   data = NULL;
1958   GST_LOG_OBJECT (qtmux, "Copying movie header into buffer");
1959   if (!atom_moov_copy_data (qtmux->moov, &data, &size, &offset))
1960     goto serialize_error;
1961   qtmux->last_moov_size = offset;
1962
1963   /* Check we have enough reserved space for this and a Free atom */
1964   if (padded_moov_size > 0 && offset + 8 > padded_moov_size)
1965     goto too_small_reserved;
1966   buf = _gst_buffer_new_take_data (data, offset);
1967   GST_DEBUG_OBJECT (qtmux, "Pushing moov atoms");
1968
1969   /* If at EOS, this is the final moov, put in the streamheader
1970    * (apparently used by a flumotion util) */
1971   if (qtmux->state == GST_QT_MUX_STATE_EOS)
1972     gst_qt_mux_set_header_on_caps (qtmux, buf);
1973
1974   if (fsync_after)
1975     GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_SYNC_AFTER);
1976   ret = gst_qt_mux_send_buffer (qtmux, buf, _offset, mind_fast);
1977
1978   /* Write out a free atom if needed */
1979   if (ret == GST_FLOW_OK && offset < padded_moov_size) {
1980     GST_LOG_OBJECT (qtmux, "Writing out free atom of size %u",
1981         (guint32) (padded_moov_size - offset));
1982     ret =
1983         gst_qt_mux_send_free_atom (qtmux, _offset, padded_moov_size - offset,
1984         fsync_after);
1985   }
1986
1987   return ret;
1988 too_small_reserved:
1989   {
1990     GST_ELEMENT_ERROR (qtmux, STREAM, MUX,
1991         ("Not enough free reserved header space"),
1992         ("Needed %" G_GUINT64_FORMAT " bytes, reserved %" G_GUINT64_FORMAT,
1993             offset, padded_moov_size));
1994     return GST_FLOW_ERROR;
1995   }
1996 serialize_error:
1997   {
1998     g_free (data);
1999     return GST_FLOW_ERROR;
2000   }
2001 }
2002
2003 /* either calculates size of extra atoms or pushes them */
2004 static GstFlowReturn
2005 gst_qt_mux_send_extra_atoms (GstQTMux * qtmux, gboolean send, guint64 * offset,
2006     gboolean mind_fast)
2007 {
2008   GSList *walk;
2009   guint64 loffset = 0, size = 0;
2010   guint8 *data;
2011   GstFlowReturn ret = GST_FLOW_OK;
2012
2013   for (walk = qtmux->extra_atoms; walk; walk = g_slist_next (walk)) {
2014     AtomInfo *ainfo = (AtomInfo *) walk->data;
2015
2016     loffset = size = 0;
2017     data = NULL;
2018     if (!ainfo->copy_data_func (ainfo->atom,
2019             send ? &data : NULL, &size, &loffset))
2020       goto serialize_error;
2021
2022     if (send) {
2023       GstBuffer *buf;
2024
2025       GST_DEBUG_OBJECT (qtmux,
2026           "Pushing extra top-level atom %" GST_FOURCC_FORMAT,
2027           GST_FOURCC_ARGS (ainfo->atom->type));
2028       buf = _gst_buffer_new_take_data (data, loffset);
2029       ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
2030       if (ret != GST_FLOW_OK)
2031         break;
2032     } else {
2033       if (offset)
2034         *offset += loffset;
2035     }
2036   }
2037
2038   return ret;
2039
2040 serialize_error:
2041   {
2042     g_free (data);
2043     return GST_FLOW_ERROR;
2044   }
2045 }
2046
2047 static gboolean
2048 gst_qt_mux_downstream_is_seekable (GstQTMux * qtmux)
2049 {
2050   gboolean seekable = FALSE;
2051   GstQuery *query = gst_query_new_seeking (GST_FORMAT_BYTES);
2052
2053   if (gst_pad_peer_query (qtmux->srcpad, query)) {
2054     gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
2055     GST_INFO_OBJECT (qtmux, "downstream is %sseekable", seekable ? "" : "not ");
2056   } else {
2057     /* have to assume seeking is not supported if query not handled downstream */
2058     GST_WARNING_OBJECT (qtmux, "downstream did not handle seeking query");
2059     seekable = FALSE;
2060   }
2061   gst_query_unref (query);
2062
2063   return seekable;
2064 }
2065
2066 static void
2067 gst_qt_mux_prepare_moov_recovery (GstQTMux * qtmux)
2068 {
2069   GSList *walk;
2070   gboolean fail = FALSE;
2071   AtomFTYP *ftyp = NULL;
2072   GstBuffer *prefix = NULL;
2073
2074   GST_DEBUG_OBJECT (qtmux, "Opening moov recovery file: %s",
2075       qtmux->moov_recov_file_path);
2076
2077   qtmux->moov_recov_file = g_fopen (qtmux->moov_recov_file_path, "wb+");
2078   if (qtmux->moov_recov_file == NULL) {
2079     GST_WARNING_OBJECT (qtmux, "Failed to open moov recovery file in %s",
2080         qtmux->moov_recov_file_path);
2081     return;
2082   }
2083
2084   gst_qt_mux_prepare_ftyp (qtmux, &ftyp, &prefix);
2085
2086   if (!atoms_recov_write_headers (qtmux->moov_recov_file, ftyp, prefix,
2087           qtmux->moov, qtmux->timescale, g_slist_length (qtmux->sinkpads))) {
2088     GST_WARNING_OBJECT (qtmux, "Failed to write moov recovery file " "headers");
2089     goto fail;
2090   }
2091
2092   atom_ftyp_free (ftyp);
2093   if (prefix)
2094     gst_buffer_unref (prefix);
2095
2096   for (walk = qtmux->sinkpads; walk && !fail; walk = g_slist_next (walk)) {
2097     GstCollectData *cdata = (GstCollectData *) walk->data;
2098     GstQTPad *qpad = (GstQTPad *) cdata;
2099     /* write info for each stream */
2100     fail = atoms_recov_write_trak_info (qtmux->moov_recov_file, qpad->trak);
2101     if (fail) {
2102       GST_WARNING_OBJECT (qtmux, "Failed to write trak info to recovery "
2103           "file");
2104       break;
2105     }
2106   }
2107
2108   return;
2109
2110 fail:
2111   /* cleanup */
2112   fclose (qtmux->moov_recov_file);
2113   qtmux->moov_recov_file = NULL;
2114 }
2115
2116 static GstFlowReturn
2117 gst_qt_mux_start_file (GstQTMux * qtmux)
2118 {
2119   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
2120   GstFlowReturn ret = GST_FLOW_OK;
2121   GstCaps *caps;
2122   GstSegment segment;
2123   gchar s_id[32];
2124   GstClockTime reserved_max_duration;
2125   guint reserved_bytes_per_sec_per_trak;
2126
2127   GST_DEBUG_OBJECT (qtmux, "starting file");
2128
2129   GST_OBJECT_LOCK (qtmux);
2130   reserved_max_duration = qtmux->reserved_max_duration;
2131   reserved_bytes_per_sec_per_trak = qtmux->reserved_bytes_per_sec_per_trak;
2132   GST_OBJECT_UNLOCK (qtmux);
2133
2134   /* stream-start (FIXME: create id based on input ids) */
2135   g_snprintf (s_id, sizeof (s_id), "qtmux-%08x", g_random_int ());
2136   gst_pad_push_event (qtmux->srcpad, gst_event_new_stream_start (s_id));
2137
2138   caps = gst_caps_copy (gst_pad_get_pad_template_caps (qtmux->srcpad));
2139   /* qtmux has structure with and without variant, remove all but the first */
2140   while (gst_caps_get_size (caps) > 1)
2141     gst_caps_remove_structure (caps, 1);
2142   gst_pad_set_caps (qtmux->srcpad, caps);
2143   gst_caps_unref (caps);
2144
2145   /* Default is 'normal' mode */
2146   qtmux->mux_mode = GST_QT_MUX_MODE_MOOV_AT_END;
2147
2148   /* Require a sensible fragment duration when muxing
2149    * using the ISML muxer */
2150   if (qtmux_klass->format == GST_QT_MUX_FORMAT_ISML &&
2151       qtmux->fragment_duration == 0)
2152     goto invalid_isml;
2153
2154   if (qtmux->fragment_duration > 0) {
2155     if (qtmux->streamable)
2156       qtmux->mux_mode = GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE;
2157     else
2158       qtmux->mux_mode = GST_QT_MUX_MODE_FRAGMENTED;
2159   } else if (qtmux->fast_start) {
2160     qtmux->mux_mode = GST_QT_MUX_MODE_FAST_START;
2161   } else if (reserved_max_duration != GST_CLOCK_TIME_NONE) {
2162     qtmux->mux_mode = GST_QT_MUX_MODE_ROBUST_RECORDING;
2163   }
2164
2165   switch (qtmux->mux_mode) {
2166     case GST_QT_MUX_MODE_MOOV_AT_END:
2167     case GST_QT_MUX_MODE_ROBUST_RECORDING:
2168       /* We have to be able to seek to rewrite the mdat header, or any
2169        * moov atom we write will not be visible in the file, because an
2170        * MDAT with 0 as the size covers the rest of the file. A file
2171        * with no moov is not playable, so error out now. */
2172       if (!gst_qt_mux_downstream_is_seekable (qtmux)) {
2173         GST_ELEMENT_ERROR (qtmux, STREAM, MUX,
2174             ("Downstream is not seekable - will not be able to create a playable file"),
2175             (NULL));
2176         return GST_FLOW_ERROR;
2177       }
2178       break;
2179     case GST_QT_MUX_MODE_FAST_START:
2180     case GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE:
2181       break;                    /* Don't need seekability, ignore */
2182     case GST_QT_MUX_MODE_FRAGMENTED:
2183       if (!gst_qt_mux_downstream_is_seekable (qtmux)) {
2184         GST_WARNING_OBJECT (qtmux, "downstream is not seekable, but "
2185             "streamable=false. Will ignore that and create streamable output "
2186             "instead");
2187         qtmux->streamable = TRUE;
2188         g_object_notify (G_OBJECT (qtmux), "streamable");
2189       }
2190       break;
2191   }
2192
2193   /* let downstream know we think in BYTES and expect to do seeking later on */
2194   gst_segment_init (&segment, GST_FORMAT_BYTES);
2195   gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
2196
2197   GST_OBJECT_LOCK (qtmux);
2198
2199   if (qtmux->timescale == 0) {
2200     guint32 suggested_timescale = 0;
2201     GSList *walk;
2202
2203     /* Calculate a reasonable timescale for the moov:
2204      * If there is video, it is the biggest video track timescale or an even
2205      * multiple of it if it's smaller than 1800.
2206      * Otherwise it is 1800 */
2207     for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
2208       GstCollectData *cdata = (GstCollectData *) walk->data;
2209       GstQTPad *qpad = (GstQTPad *) cdata;
2210
2211       if (!qpad->trak)
2212         continue;
2213
2214       /* not video */
2215       if (!qpad->trak->mdia.minf.vmhd)
2216         continue;
2217
2218       suggested_timescale =
2219           MAX (qpad->trak->mdia.mdhd.time_info.timescale, suggested_timescale);
2220     }
2221
2222     if (suggested_timescale == 0)
2223       suggested_timescale = 1800;
2224
2225     while (suggested_timescale < 1800)
2226       suggested_timescale *= 2;
2227
2228     qtmux->timescale = suggested_timescale;
2229   }
2230
2231   /* initialize our moov recovery file */
2232   if (qtmux->moov_recov_file_path) {
2233     gst_qt_mux_prepare_moov_recovery (qtmux);
2234   }
2235
2236   /* Make sure the first time we update the moov, we'll
2237    * include any tagsetter tags */
2238   qtmux->tags_changed = TRUE;
2239
2240   GST_OBJECT_UNLOCK (qtmux);
2241
2242   /*
2243    * send mdat header if already needed, and mark position for later update.
2244    * We don't send ftyp now if we are on fast start mode, because we can
2245    * better fine tune using the information we gather to create the whole moov
2246    * atom.
2247    */
2248   switch (qtmux->mux_mode) {
2249     case GST_QT_MUX_MODE_MOOV_AT_END:
2250       ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
2251       if (ret != GST_FLOW_OK)
2252         break;
2253
2254       /* Store this as the mdat offset for later updating
2255        * when we write the moov */
2256       qtmux->mdat_pos = qtmux->header_size;
2257       /* extended atom in case we go over 4GB while writing and need
2258        * the full 64-bit atom */
2259       ret =
2260           gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE,
2261           FALSE);
2262       break;
2263     case GST_QT_MUX_MODE_ROBUST_RECORDING:
2264
2265       ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
2266       if (ret != GST_FLOW_OK)
2267         break;
2268
2269       /* Pad ftyp out to an 8-byte boundary before starting the moov
2270        * ping pong region. It should be well less than 1 disk sector,
2271        * unless there's a bajillion compatible types listed,
2272        * but let's be sure the free atom doesn't cross a sector
2273        * boundary anyway */
2274       if (qtmux->header_size % 8) {
2275         /* Extra 8 bytes for the padding free atom header */
2276         guint padding = (guint) (16 - (qtmux->header_size % 8));
2277         GST_LOG_OBJECT (qtmux, "Rounding ftyp by %u bytes", padding);
2278         ret =
2279             gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size, padding,
2280             FALSE);
2281         if (ret != GST_FLOW_OK)
2282           return ret;
2283       }
2284
2285       /* Store this as the moov offset for later updating.
2286        * We record mdat position below */
2287       qtmux->moov_pos = qtmux->header_size;
2288
2289       /* Set up the initial 'ping' state of the ping-pong buffers */
2290       qtmux->reserved_moov_first_active = TRUE;
2291
2292       gst_qt_mux_configure_moov (qtmux);
2293       gst_qt_mux_setup_metadata (qtmux);
2294       /* Empty free atom to begin, starting on an 8-byte boundary */
2295       ret = gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size, 8, FALSE);
2296       if (ret != GST_FLOW_OK)
2297         return ret;
2298       /* Moov header, not padded yet */
2299       ret = gst_qt_mux_send_moov (qtmux, &qtmux->header_size, 0, FALSE, FALSE);
2300       if (ret != GST_FLOW_OK)
2301         return ret;
2302       /* The moov we just sent contains the 'base' size of the moov, before
2303        * we put in any time-dependent per-trak data. Use that to make
2304        * a good estimate of how much extra to reserve */
2305       /* Calculate how much space to reserve for our MOOV atom.
2306        * We actually reserve twice that, for ping-pong buffers */
2307       qtmux->base_moov_size = qtmux->last_moov_size;
2308       GST_LOG_OBJECT (qtmux, "Base moov size is %u before any indexes",
2309           qtmux->base_moov_size);
2310       qtmux->reserved_moov_size = qtmux->base_moov_size +
2311           gst_util_uint64_scale (reserved_max_duration,
2312           reserved_bytes_per_sec_per_trak *
2313           atom_moov_get_trak_count (qtmux->moov), GST_SECOND);
2314
2315       /* Need space for at least 4 atom headers. More really, but
2316        * this as an absolute minimum */
2317       if (qtmux->reserved_moov_size < 4 * 8)
2318         goto reserved_moov_too_small;
2319
2320       GST_DEBUG_OBJECT (qtmux, "reserving header area of size %u",
2321           2 * qtmux->reserved_moov_size + 16);
2322
2323       GST_OBJECT_LOCK (qtmux);
2324       qtmux->reserved_duration_remaining =
2325           gst_util_uint64_scale (qtmux->reserved_moov_size -
2326           qtmux->base_moov_size, GST_SECOND,
2327           reserved_bytes_per_sec_per_trak *
2328           atom_moov_get_trak_count (qtmux->moov));
2329       GST_OBJECT_UNLOCK (qtmux);
2330
2331       /* Now that we know how much reserved space is targetted,
2332        * output a free atom to fill the extra reserved */
2333       ret = gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size,
2334           qtmux->reserved_moov_size - qtmux->base_moov_size, FALSE);
2335       if (ret != GST_FLOW_OK)
2336         return ret;
2337
2338       /* Then a free atom containing 'pong' buffer, with an
2339        * extra 8 bytes to account for the free atom header itself */
2340       ret = gst_qt_mux_send_free_atom (qtmux, &qtmux->header_size,
2341           qtmux->reserved_moov_size + 8, FALSE);
2342       if (ret != GST_FLOW_OK)
2343         return ret;
2344
2345       /* extra atoms go after the free/moov(s), before the mdat */
2346       ret =
2347           gst_qt_mux_send_extra_atoms (qtmux, TRUE, &qtmux->header_size, FALSE);
2348       if (ret != GST_FLOW_OK)
2349         return ret;
2350
2351       qtmux->mdat_pos = qtmux->header_size;
2352       /* extended atom in case we go over 4GB while writing and need
2353        * the full 64-bit atom */
2354       ret =
2355           gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE,
2356           FALSE);
2357       break;
2358     case GST_QT_MUX_MODE_FAST_START:
2359       GST_OBJECT_LOCK (qtmux);
2360       qtmux->fast_start_file = g_fopen (qtmux->fast_start_file_path, "wb+");
2361       if (!qtmux->fast_start_file)
2362         goto open_failed;
2363       GST_OBJECT_UNLOCK (qtmux);
2364       /* send a dummy buffer for preroll */
2365       ret = gst_qt_mux_send_buffer (qtmux, gst_buffer_new (), NULL, FALSE);
2366       break;
2367     case GST_QT_MUX_MODE_FRAGMENTED:
2368     case GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE:
2369       ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
2370       if (ret != GST_FLOW_OK)
2371         break;
2372       /* store the moov pos so we can update the duration later
2373        * in non-streamable mode */
2374       qtmux->moov_pos = qtmux->header_size;
2375
2376       GST_DEBUG_OBJECT (qtmux, "fragment duration %d ms, writing headers",
2377           qtmux->fragment_duration);
2378       /* also used as snapshot marker to indicate fragmented file */
2379       qtmux->fragment_sequence = 1;
2380       /* prepare moov and/or tags */
2381       gst_qt_mux_configure_moov (qtmux);
2382       gst_qt_mux_setup_metadata (qtmux);
2383       ret = gst_qt_mux_send_moov (qtmux, &qtmux->header_size, 0, FALSE, FALSE);
2384       if (ret != GST_FLOW_OK)
2385         return ret;
2386       /* extra atoms */
2387       ret =
2388           gst_qt_mux_send_extra_atoms (qtmux, TRUE, &qtmux->header_size, FALSE);
2389       if (ret != GST_FLOW_OK)
2390         break;
2391       /* prepare index if not streamable */
2392       if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED)
2393         qtmux->mfra = atom_mfra_new (qtmux->context);
2394       break;
2395   }
2396
2397   return ret;
2398   /* ERRORS */
2399 invalid_isml:
2400   {
2401     GST_ELEMENT_ERROR (qtmux, STREAM, MUX,
2402         ("Cannot create an ISML file with 0 fragment duration"), (NULL));
2403     return GST_FLOW_ERROR;
2404   }
2405 reserved_moov_too_small:
2406   {
2407     GST_ELEMENT_ERROR (qtmux, STREAM, MUX,
2408         ("Not enough reserved space for creating headers"), (NULL));
2409     return GST_FLOW_ERROR;
2410   }
2411 open_failed:
2412   {
2413     GST_ELEMENT_ERROR (qtmux, RESOURCE, OPEN_READ_WRITE,
2414         (("Could not open temporary file \"%s\""),
2415             qtmux->fast_start_file_path), GST_ERROR_SYSTEM);
2416     GST_OBJECT_UNLOCK (qtmux);
2417     return GST_FLOW_ERROR;
2418   }
2419 }
2420
2421 static GstFlowReturn
2422 gst_qt_mux_send_last_buffers (GstQTMux * qtmux)
2423 {
2424   GstFlowReturn ret = GST_FLOW_OK;
2425   GSList *walk;
2426
2427   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
2428     GstCollectData *cdata = (GstCollectData *) walk->data;
2429     GstQTPad *qtpad = (GstQTPad *) cdata;
2430
2431     /* avoid add_buffer complaining if not negotiated
2432      * in which case no buffers either, so skipping */
2433     if (!qtpad->fourcc) {
2434       GST_DEBUG_OBJECT (qtmux, "Pad %s has never had buffers",
2435           GST_PAD_NAME (qtpad->collect.pad));
2436       continue;
2437     }
2438
2439     /* send last buffer; also flushes possibly queued buffers/ts */
2440     GST_DEBUG_OBJECT (qtmux, "Sending the last buffer for pad %s",
2441         GST_PAD_NAME (qtpad->collect.pad));
2442     ret = gst_qt_mux_add_buffer (qtmux, qtpad, NULL);
2443     if (ret != GST_FLOW_OK) {
2444       GST_WARNING_OBJECT (qtmux, "Failed to send last buffer for %s, "
2445           "flow return: %s", GST_PAD_NAME (qtpad->collect.pad),
2446           gst_flow_get_name (ret));
2447     }
2448   }
2449
2450   return ret;
2451 }
2452
2453 static void
2454 gst_qt_mux_update_global_statistics (GstQTMux * qtmux)
2455 {
2456   GSList *walk;
2457
2458   /* for setting some subtitles fields */
2459   guint max_width = 0;
2460   guint max_height = 0;
2461
2462   qtmux->first_ts = qtmux->last_dts = GST_CLOCK_TIME_NONE;
2463
2464   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
2465     GstCollectData *cdata = (GstCollectData *) walk->data;
2466     GstQTPad *qtpad = (GstQTPad *) cdata;
2467
2468     if (!qtpad->fourcc) {
2469       GST_DEBUG_OBJECT (qtmux, "Pad %s has never had buffers",
2470           GST_PAD_NAME (qtpad->collect.pad));
2471       continue;
2472     }
2473
2474     /* having flushed above, can check for buffers now */
2475     if (GST_CLOCK_TIME_IS_VALID (qtpad->first_ts)) {
2476       /* determine max stream duration */
2477       if (!GST_CLOCK_TIME_IS_VALID (qtmux->last_dts)
2478           || qtpad->last_dts > qtmux->last_dts) {
2479         qtmux->last_dts = qtpad->last_dts;
2480       }
2481       if (!GST_CLOCK_TIME_IS_VALID (qtmux->first_ts)
2482           || qtpad->first_ts < qtmux->first_ts) {
2483         qtmux->first_ts = qtpad->first_ts;
2484       }
2485     }
2486
2487     /* subtitles need to know the video width/height,
2488      * it is stored shifted 16 bits to the left according to the
2489      * spec */
2490     max_width = MAX (max_width, (qtpad->trak->tkhd.width >> 16));
2491     max_height = MAX (max_height, (qtpad->trak->tkhd.height >> 16));
2492
2493     /* update average bitrate of streams if needed */
2494     {
2495       guint32 avgbitrate = 0;
2496       guint32 maxbitrate = qtpad->max_bitrate;
2497
2498       if (qtpad->avg_bitrate)
2499         avgbitrate = qtpad->avg_bitrate;
2500       else if (qtpad->total_duration > 0)
2501         avgbitrate = (guint32) gst_util_uint64_scale_round (qtpad->total_bytes,
2502             8 * GST_SECOND, qtpad->total_duration);
2503
2504       atom_trak_update_bitrates (qtpad->trak, avgbitrate, maxbitrate);
2505     }
2506   }
2507
2508   /* need to update values on subtitle traks now that we know the
2509    * max width and height */
2510   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
2511     GstCollectData *cdata = (GstCollectData *) walk->data;
2512     GstQTPad *qtpad = (GstQTPad *) cdata;
2513
2514     if (!qtpad->fourcc) {
2515       GST_DEBUG_OBJECT (qtmux, "Pad %s has never had buffers",
2516           GST_PAD_NAME (qtpad->collect.pad));
2517       continue;
2518     }
2519
2520     if (qtpad->fourcc == FOURCC_tx3g) {
2521       atom_trak_tx3g_update_dimension (qtpad->trak, max_width, max_height);
2522     }
2523   }
2524 }
2525
2526 /* Called after gst_qt_mux_update_global_statistics() updates the
2527  * first_ts tracking, to create/set edit lists for delayed streams */
2528 static void
2529 gst_qt_mux_update_edit_lists (GstQTMux * qtmux)
2530 {
2531   GSList *walk;
2532
2533   GST_DEBUG_OBJECT (qtmux, "Media first ts selected: %" GST_TIME_FORMAT,
2534       GST_TIME_ARGS (qtmux->first_ts));
2535   /* add/update EDTSs for late streams. configure_moov will have
2536    * set the trak durations above by summing the sample tables,
2537    * here we extend that if needing to insert an empty segment */
2538   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
2539     GstCollectData *cdata = (GstCollectData *) walk->data;
2540     GstQTPad *qtpad = (GstQTPad *) cdata;
2541
2542     atom_trak_edts_clear (qtpad->trak);
2543
2544     if (GST_CLOCK_TIME_IS_VALID (qtpad->first_ts)) {
2545       guint32 lateness = 0;
2546       guint32 duration = qtpad->trak->tkhd.duration;
2547       gboolean has_gap;
2548
2549       has_gap = (qtpad->first_ts > (qtmux->first_ts + qtpad->dts_adjustment));
2550
2551       if (has_gap) {
2552         GstClockTime diff;
2553
2554         diff = qtpad->first_ts - (qtmux->first_ts + qtpad->dts_adjustment);
2555         lateness = gst_util_uint64_scale_round (diff,
2556             qtmux->timescale, GST_SECOND);
2557
2558         if (lateness > 0) {
2559           GST_DEBUG_OBJECT (qtmux,
2560               "Pad %s is a late stream by %" GST_TIME_FORMAT,
2561               GST_PAD_NAME (qtpad->collect.pad), GST_TIME_ARGS (lateness));
2562
2563           atom_trak_set_elst_entry (qtpad->trak, 0, lateness, (guint32) - 1,
2564               (guint32) (1 * 65536.0));
2565         }
2566       }
2567
2568       /* Always write an edit list for the whole track. In general this is not
2569        * necessary except for the case of having a gap or DTS adjustment but
2570        * it allows to give the whole track's duration in the usually more
2571        * accurate media timescale
2572        */
2573       {
2574         GstClockTime ctts = 0;
2575         guint32 media_start;
2576
2577         if (qtpad->first_ts > qtpad->first_dts)
2578           ctts = qtpad->first_ts - qtpad->first_dts;
2579
2580         media_start = gst_util_uint64_scale_round (ctts,
2581             atom_trak_get_timescale (qtpad->trak), GST_SECOND);
2582
2583         /* atom_trak_set_elst_entry() has a quirk - if the edit list
2584          * is empty because there's no gap added above, this call
2585          * will not replace index 1, it will create the entry at index 0.
2586          * Luckily, that's exactly what we want here */
2587         atom_trak_set_elst_entry (qtpad->trak, 1, duration, media_start,
2588             (guint32) (1 * 65536.0));
2589       }
2590
2591       /* need to add the empty time to the trak duration */
2592       duration += lateness;
2593       qtpad->trak->tkhd.duration = duration;
2594       if (qtpad->tc_trak) {
2595         qtpad->tc_trak->tkhd.duration = duration;
2596         qtpad->tc_trak->mdia.mdhd.time_info.duration = duration;
2597       }
2598
2599       /* And possibly grow the moov duration */
2600       if (duration > qtmux->moov->mvhd.time_info.duration) {
2601         qtmux->moov->mvhd.time_info.duration = duration;
2602         qtmux->moov->mvex.mehd.fragment_duration = duration;
2603       }
2604     }
2605   }
2606 }
2607
2608 static GstFlowReturn
2609 gst_qt_mux_update_timecode (GstQTMux * qtmux, GstQTPad * qtpad)
2610 {
2611   GstSegment segment;
2612   GstBuffer *buf;
2613   GstMapInfo map;
2614   guint64 offset = qtpad->tc_pos;
2615
2616   g_assert (qtpad->tc_pos != -1);
2617
2618   gst_segment_init (&segment, GST_FORMAT_BYTES);
2619   segment.start = offset;
2620   gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
2621
2622   buf = gst_buffer_new_and_alloc (4);
2623   gst_buffer_map (buf, &map, GST_MAP_WRITE);
2624
2625   GST_WRITE_UINT32_BE (map.data,
2626       gst_video_time_code_frames_since_daily_jam (qtpad->first_tc));
2627   gst_buffer_unmap (buf, &map);
2628
2629   /* Reset this value, so the timecode won't be re-rewritten */
2630   qtpad->tc_pos = -1;
2631
2632   return gst_qt_mux_send_buffer (qtmux, buf, &offset, FALSE);
2633 }
2634
2635 static GstFlowReturn
2636 gst_qt_mux_stop_file (GstQTMux * qtmux)
2637 {
2638   gboolean ret = GST_FLOW_OK;
2639   guint64 offset = 0, size = 0;
2640   gboolean large_file;
2641   GSList *walk;
2642
2643   GST_DEBUG_OBJECT (qtmux, "Updating remaining values and sending last data");
2644
2645   /* pushing last buffers for each pad */
2646   if ((ret = gst_qt_mux_send_last_buffers (qtmux)) != GST_FLOW_OK)
2647     return ret;
2648
2649   if (qtmux->mux_mode == GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE) {
2650     /* Streamable mode; no need to write duration or MFRA */
2651     GST_DEBUG_OBJECT (qtmux, "streamable file; nothing to stop");
2652     return GST_FLOW_OK;
2653   }
2654
2655   gst_qt_mux_update_global_statistics (qtmux);
2656   for (walk = qtmux->collect->data; walk; walk = walk->next) {
2657     GstQTPad *qtpad = (GstQTPad *) walk->data;
2658
2659     if (qtpad->tc_pos != -1) {
2660       /* File is being stopped and timecode hasn't been updated. Update it now
2661        * with whatever we have */
2662       ret = gst_qt_mux_update_timecode (qtmux, qtpad);
2663       if (ret != GST_FLOW_OK)
2664         return ret;
2665     }
2666   }
2667
2668   switch (qtmux->mux_mode) {
2669     case GST_QT_MUX_MODE_FRAGMENTED:{
2670       GstSegment segment;
2671       guint8 *data = NULL;
2672       GstBuffer *buf;
2673
2674       size = offset = 0;
2675       GST_DEBUG_OBJECT (qtmux, "adding mfra");
2676       if (!atom_mfra_copy_data (qtmux->mfra, &data, &size, &offset))
2677         goto serialize_error;
2678       buf = _gst_buffer_new_take_data (data, offset);
2679       ret = gst_qt_mux_send_buffer (qtmux, buf, NULL, FALSE);
2680       if (ret != GST_FLOW_OK)
2681         return ret;
2682
2683       /* only mvex duration is updated,
2684        * mvhd should be consistent with empty moov
2685        * (but TODO maybe some clients do not handle that well ?) */
2686       qtmux->moov->mvex.mehd.fragment_duration =
2687           gst_util_uint64_scale (qtmux->last_dts, qtmux->timescale, GST_SECOND);
2688       GST_DEBUG_OBJECT (qtmux, "rewriting moov with mvex duration %"
2689           GST_TIME_FORMAT, GST_TIME_ARGS (qtmux->last_dts));
2690       /* seek and rewrite the header */
2691       gst_segment_init (&segment, GST_FORMAT_BYTES);
2692       segment.start = qtmux->moov_pos;
2693       gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
2694       /* no need to seek back */
2695       return gst_qt_mux_send_moov (qtmux, NULL, 0, FALSE, FALSE);
2696     }
2697     case GST_QT_MUX_MODE_ROBUST_RECORDING:{
2698       ret = gst_qt_mux_robust_recording_rewrite_moov (qtmux);
2699       if (G_UNLIKELY (ret != GST_FLOW_OK))
2700         return ret;
2701       /* Finalise by writing the final size into the mdat. Up until now
2702        * it's been 0, which means 'rest of the file'
2703        * No need to seek back after this, we won't write any more */
2704       return gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
2705           qtmux->mdat_size, NULL, TRUE);
2706     }
2707     default:
2708       break;
2709   }
2710
2711   /* Moov-at-end or fast-start mode from here down */
2712   gst_qt_mux_configure_moov (qtmux);
2713
2714   gst_qt_mux_update_edit_lists (qtmux);
2715
2716   /* tags into file metadata */
2717   gst_qt_mux_setup_metadata (qtmux);
2718
2719   large_file = (qtmux->mdat_size > MDAT_LARGE_FILE_LIMIT);
2720
2721   switch (qtmux->mux_mode) {
2722     case GST_QT_MUX_MODE_FAST_START:{
2723       /* if faststart, update the offset of the atoms in the movie with the offset
2724        * that the movie headers before mdat will cause.
2725        * Also, send the ftyp */
2726       offset = size = 0;
2727
2728       ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
2729       if (ret != GST_FLOW_OK) {
2730         goto ftyp_error;
2731       }
2732       /* copy into NULL to obtain size */
2733       if (!atom_moov_copy_data (qtmux->moov, NULL, &size, &offset))
2734         goto serialize_error;
2735       GST_DEBUG_OBJECT (qtmux, "calculated moov atom size %" G_GUINT64_FORMAT,
2736           offset);
2737       offset += qtmux->header_size + (large_file ? 16 : 8);
2738
2739       /* sum up with the extra atoms size */
2740       ret = gst_qt_mux_send_extra_atoms (qtmux, FALSE, &offset, FALSE);
2741       if (ret != GST_FLOW_OK)
2742         return ret;
2743       break;
2744     }
2745     default:
2746       offset = qtmux->header_size;
2747       break;
2748   }
2749
2750   /* Now that we know the size of moov + extra atoms, we can adjust
2751    * the chunk offsets stored into the moov */
2752   atom_moov_chunks_set_offset (qtmux->moov, offset);
2753
2754   /* write out moov and extra atoms */
2755   /* note: as of this point, we no longer care about tracking written data size,
2756    * since there is no more use for it anyway */
2757   ret = gst_qt_mux_send_moov (qtmux, NULL, 0, FALSE, FALSE);
2758   if (ret != GST_FLOW_OK)
2759     return ret;
2760
2761   /* extra atoms */
2762   ret = gst_qt_mux_send_extra_atoms (qtmux, TRUE, NULL, FALSE);
2763   if (ret != GST_FLOW_OK)
2764     return ret;
2765
2766   switch (qtmux->mux_mode) {
2767     case GST_QT_MUX_MODE_MOOV_AT_END:
2768     {
2769       /* mdat needs update iff not using faststart */
2770       GST_DEBUG_OBJECT (qtmux, "updating mdat size");
2771       ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
2772           qtmux->mdat_size, NULL, FALSE);
2773       /* note; no seeking back to the end of file is done,
2774        * since we no longer write anything anyway */
2775       break;
2776     }
2777     case GST_QT_MUX_MODE_FAST_START:
2778     {
2779       /* send mdat atom and move buffered data into it */
2780       /* mdat_size = accumulated (buffered data) */
2781       ret = gst_qt_mux_send_mdat_header (qtmux, NULL, qtmux->mdat_size,
2782           large_file, FALSE);
2783       if (ret != GST_FLOW_OK)
2784         return ret;
2785       ret = gst_qt_mux_send_buffered_data (qtmux, NULL);
2786       if (ret != GST_FLOW_OK)
2787         return ret;
2788       break;
2789     }
2790     default:
2791       g_assert_not_reached ();
2792   }
2793
2794   return ret;
2795
2796   /* ERRORS */
2797 serialize_error:
2798   {
2799     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
2800         ("Failed to serialize moov"));
2801     return GST_FLOW_ERROR;
2802   }
2803 ftyp_error:
2804   {
2805     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), ("Failed to send ftyp"));
2806     return GST_FLOW_ERROR;
2807   }
2808 }
2809
2810 static GstFlowReturn
2811 gst_qt_mux_pad_fragment_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
2812     GstBuffer * buf, gboolean force, guint32 nsamples, gint64 dts,
2813     guint32 delta, guint32 size, gboolean sync, gint64 pts_offset)
2814 {
2815   GstFlowReturn ret = GST_FLOW_OK;
2816
2817   /* setup if needed */
2818   if (G_UNLIKELY (!pad->traf || force))
2819     goto init;
2820
2821 flush:
2822   /* flush pad fragment if threshold reached,
2823    * or at new keyframe if we should be minding those in the first place */
2824   if (G_UNLIKELY (force || (sync && pad->sync) ||
2825           pad->fragment_duration < (gint64) delta)) {
2826     AtomMOOF *moof;
2827     guint64 size = 0, offset = 0;
2828     guint8 *data = NULL;
2829     GstBuffer *buffer;
2830     guint i, total_size;
2831
2832     /* now we know where moof ends up, update offset in tfra */
2833     if (pad->tfra)
2834       atom_tfra_update_offset (pad->tfra, qtmux->header_size);
2835
2836     moof = atom_moof_new (qtmux->context, qtmux->fragment_sequence);
2837     /* takes ownership */
2838     atom_moof_add_traf (moof, pad->traf);
2839     pad->traf = NULL;
2840     atom_moof_copy_data (moof, &data, &size, &offset);
2841     buffer = _gst_buffer_new_take_data (data, offset);
2842     GST_LOG_OBJECT (qtmux, "writing moof size %" G_GSIZE_FORMAT,
2843         gst_buffer_get_size (buffer));
2844     ret = gst_qt_mux_send_buffer (qtmux, buffer, &qtmux->header_size, FALSE);
2845
2846     /* and actual data */
2847     total_size = 0;
2848     for (i = 0; i < atom_array_get_len (&pad->fragment_buffers); i++) {
2849       total_size +=
2850           gst_buffer_get_size (atom_array_index (&pad->fragment_buffers, i));
2851     }
2852
2853     GST_LOG_OBJECT (qtmux, "writing %d buffers, total_size %d",
2854         atom_array_get_len (&pad->fragment_buffers), total_size);
2855     if (ret == GST_FLOW_OK)
2856       ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, total_size,
2857           FALSE, FALSE);
2858     for (i = 0; i < atom_array_get_len (&pad->fragment_buffers); i++) {
2859       if (G_LIKELY (ret == GST_FLOW_OK))
2860         ret = gst_qt_mux_send_buffer (qtmux,
2861             atom_array_index (&pad->fragment_buffers, i), &qtmux->header_size,
2862             FALSE);
2863       else
2864         gst_buffer_unref (atom_array_index (&pad->fragment_buffers, i));
2865     }
2866
2867     atom_array_clear (&pad->fragment_buffers);
2868     atom_moof_free (moof);
2869     qtmux->fragment_sequence++;
2870     force = FALSE;
2871   }
2872
2873 init:
2874   if (G_UNLIKELY (!pad->traf)) {
2875     GST_LOG_OBJECT (qtmux, "setting up new fragment");
2876     pad->traf = atom_traf_new (qtmux->context, atom_trak_get_id (pad->trak));
2877     atom_array_init (&pad->fragment_buffers, 512);
2878     pad->fragment_duration = gst_util_uint64_scale (qtmux->fragment_duration,
2879         atom_trak_get_timescale (pad->trak), 1000);
2880
2881     if (G_UNLIKELY (qtmux->mfra && !pad->tfra)) {
2882       pad->tfra = atom_tfra_new (qtmux->context, atom_trak_get_id (pad->trak));
2883       atom_mfra_add_tfra (qtmux->mfra, pad->tfra);
2884     }
2885     atom_traf_set_base_decode_time (pad->traf, dts);
2886   }
2887
2888   /* add buffer and metadata */
2889   atom_traf_add_samples (pad->traf, delta, size, sync, pts_offset,
2890       pad->sync && sync);
2891   atom_array_append (&pad->fragment_buffers, buf, 256);
2892   pad->fragment_duration -= delta;
2893
2894   if (pad->tfra) {
2895     guint32 sn = atom_traf_get_sample_num (pad->traf);
2896
2897     if ((sync && pad->sync) || (sn == 1 && !pad->sync))
2898       atom_tfra_add_entry (pad->tfra, dts, sn);
2899   }
2900
2901   if (G_UNLIKELY (force))
2902     goto flush;
2903
2904   return ret;
2905 }
2906
2907 /* Here's the clever bit of robust recording: Updating the moov
2908  * header is done using a ping-pong scheme inside 2 blocks of size
2909  * 'reserved_moov_size' at the start of the file, in such a way that the
2910  * file on-disk is always valid if interrupted.
2911  * Inside the reserved space, we have 2 pairs of free + moov atoms
2912  * (in that order), free-A + moov-A @ offset 0 and free-B + moov-B at
2913  * at offset "reserved_moov_size".
2914  *
2915  * 1. Free-A has 0 size payload, moov-A immediately after is
2916  *    active/current, and is padded with an internal Free atom to
2917  *    end at reserved_space/2. Free-B is at reserved_space/2, sized
2918  *    to cover the remaining free space (including moov-B).
2919  * 2. We write moov-B (which is invisible inside free-B), and pad it to
2920  *    end at the end of free space. Then, we update free-A to size
2921  *    reserved_space/2 + sizeof(free-B), which hides moov-A and the
2922  *    free-B header, and makes moov-B active.
2923  * 3. Rewrite moov-A inside free-A, with padding out to free-B.
2924  *    Change the size of free-A to make moov-A active again.
2925  * 4. Rinse and repeat.
2926  *
2927  */
2928 static GstFlowReturn
2929 gst_qt_mux_robust_recording_rewrite_moov (GstQTMux * qtmux)
2930 {
2931   GstSegment segment;
2932   GstFlowReturn ret;
2933   guint64 freeA_offset;
2934   guint32 new_freeA_size;
2935   guint64 new_moov_offset;
2936
2937   /* Update moov info, then seek and rewrite the MOOV atom */
2938   gst_qt_mux_update_global_statistics (qtmux);
2939   gst_qt_mux_configure_moov (qtmux);
2940
2941   gst_qt_mux_update_edit_lists (qtmux);
2942
2943   /* tags into file metadata */
2944   gst_qt_mux_setup_metadata (qtmux);
2945
2946   /* chunks position is set relative to the first byte of the
2947    * MDAT atom payload. Set the overall offset into the file */
2948   atom_moov_chunks_set_offset (qtmux->moov, qtmux->header_size);
2949
2950   /* Calculate which moov to rewrite. qtmux->moov_pos points to
2951    * the start of the free-A header */
2952   freeA_offset = qtmux->moov_pos;
2953   if (qtmux->reserved_moov_first_active) {
2954     GST_DEBUG_OBJECT (qtmux, "Updating pong moov header");
2955     /* After this, freeA will include itself, moovA, plus the freeB
2956      * header */
2957     new_freeA_size = qtmux->reserved_moov_size + 16;
2958   } else {
2959     GST_DEBUG_OBJECT (qtmux, "Updating ping moov header");
2960     new_freeA_size = 8;
2961   }
2962   /* the moov we update is after free-A, calculate its offset */
2963   new_moov_offset = freeA_offset + new_freeA_size;
2964
2965   /* Swap ping-pong cadence marker */
2966   qtmux->reserved_moov_first_active = !qtmux->reserved_moov_first_active;
2967
2968   /* seek and rewrite the MOOV atom */
2969   gst_segment_init (&segment, GST_FORMAT_BYTES);
2970   segment.start = new_moov_offset;
2971   gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
2972
2973   ret =
2974       gst_qt_mux_send_moov (qtmux, NULL, qtmux->reserved_moov_size, FALSE,
2975       TRUE);
2976   if (ret != GST_FLOW_OK)
2977     return ret;
2978
2979   /* Update the estimated recording space remaining, based on amount used so
2980    * far and duration muxed so far */
2981   if (qtmux->last_moov_size > qtmux->base_moov_size && qtmux->last_dts > 0) {
2982     GstClockTime remain;
2983     GstClockTime time_muxed = qtmux->last_dts;
2984
2985     remain =
2986         gst_util_uint64_scale (qtmux->reserved_moov_size -
2987         qtmux->last_moov_size, time_muxed,
2988         qtmux->last_moov_size - qtmux->base_moov_size);
2989     /* Always under-estimate slightly, so users
2990      * have time to stop muxing before we run out */
2991     if (remain < GST_SECOND / 2)
2992       remain = 0;
2993     else
2994       remain -= GST_SECOND / 2;
2995
2996     GST_INFO_OBJECT (qtmux,
2997         "Reserved %u header bytes. Used %u in %" GST_TIME_FORMAT
2998         ". Remaining now %u or approx %" G_GUINT64_FORMAT " ns\n",
2999         qtmux->reserved_moov_size, qtmux->last_moov_size,
3000         GST_TIME_ARGS (qtmux->last_dts),
3001         qtmux->reserved_moov_size - qtmux->last_moov_size, remain);
3002
3003     GST_OBJECT_LOCK (qtmux);
3004     qtmux->reserved_duration_remaining = remain;
3005     qtmux->muxed_since_last_update = 0;
3006     GST_DEBUG_OBJECT (qtmux, "reserved remaining duration now %"
3007         G_GUINT64_FORMAT, qtmux->reserved_duration_remaining);
3008     GST_OBJECT_UNLOCK (qtmux);
3009   }
3010
3011
3012   /* Now update the moov-A size. Don't pass offset, since we don't need
3013    * send_free_atom() to seek for us - all our callers seek back to
3014    * where they need after this, or they don't need it */
3015   gst_segment_init (&segment, GST_FORMAT_BYTES);
3016   segment.start = freeA_offset;
3017   gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
3018
3019   ret = gst_qt_mux_send_free_atom (qtmux, NULL, new_freeA_size, TRUE);
3020
3021   return ret;
3022 }
3023
3024 static GstFlowReturn
3025 gst_qt_mux_robust_recording_update (GstQTMux * qtmux, GstClockTime position)
3026 {
3027   GstSegment segment;
3028   GstFlowReturn flow_ret;
3029
3030   guint64 mdat_offset = qtmux->mdat_pos + 16 + qtmux->mdat_size;
3031
3032   GST_OBJECT_LOCK (qtmux);
3033   if (qtmux->reserved_moov_update_period == GST_CLOCK_TIME_NONE) {
3034     GST_OBJECT_UNLOCK (qtmux);
3035     return GST_FLOW_OK;
3036   }
3037
3038   /* Update if position is > the threshold or there's been no update yet */
3039   if (qtmux->last_moov_update != GST_CLOCK_TIME_NONE &&
3040       (position <= qtmux->last_moov_update ||
3041           (position - qtmux->last_moov_update) <
3042           qtmux->reserved_moov_update_period)) {
3043     /* Update the offset of how much we've muxed, so the
3044      * report of remaining space keeps counting down */
3045     if (position > qtmux->last_moov_update &&
3046         position - qtmux->last_moov_update > qtmux->muxed_since_last_update) {
3047       GST_LOG_OBJECT (qtmux,
3048           "Muxed time %" G_GUINT64_FORMAT " since last moov update",
3049           qtmux->muxed_since_last_update);
3050       qtmux->muxed_since_last_update = position - qtmux->last_moov_update;
3051     }
3052     GST_OBJECT_UNLOCK (qtmux);
3053     return GST_FLOW_OK;         /* No update needed yet */
3054   }
3055
3056   qtmux->last_moov_update = position;
3057   GST_OBJECT_UNLOCK (qtmux);
3058
3059   GST_DEBUG_OBJECT (qtmux, "Update moov atom, position %" GST_TIME_FORMAT
3060       " mdat starts @ %" G_GUINT64_FORMAT " we were a %" G_GUINT64_FORMAT,
3061       GST_TIME_ARGS (position), qtmux->mdat_pos, mdat_offset);
3062
3063   flow_ret = gst_qt_mux_robust_recording_rewrite_moov (qtmux);
3064   if (G_UNLIKELY (flow_ret != GST_FLOW_OK))
3065     return flow_ret;
3066
3067   /* Seek back to previous position */
3068   gst_segment_init (&segment, GST_FORMAT_BYTES);
3069   segment.start = mdat_offset;
3070   gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
3071
3072   return flow_ret;
3073 }
3074
3075 static GstFlowReturn
3076 gst_qt_mux_register_and_push_sample (GstQTMux * qtmux, GstQTPad * pad,
3077     GstBuffer * buffer, gboolean is_last_buffer, guint nsamples,
3078     gint64 last_dts, gint64 scaled_duration, guint sample_size,
3079     guint64 chunk_offset, gboolean sync, gboolean do_pts, gint64 pts_offset)
3080 {
3081   GstFlowReturn ret = GST_FLOW_OK;
3082
3083   /* note that a new chunk is started each time (not fancy but works) */
3084   if (qtmux->moov_recov_file) {
3085     if (!atoms_recov_write_trak_samples (qtmux->moov_recov_file, pad->trak,
3086             nsamples, (gint32) scaled_duration, sample_size, chunk_offset, sync,
3087             do_pts, pts_offset)) {
3088       GST_WARNING_OBJECT (qtmux, "Failed to write sample information to "
3089           "recovery file, disabling recovery");
3090       fclose (qtmux->moov_recov_file);
3091       qtmux->moov_recov_file = NULL;
3092     }
3093   }
3094
3095   switch (qtmux->mux_mode) {
3096     case GST_QT_MUX_MODE_MOOV_AT_END:
3097     case GST_QT_MUX_MODE_FAST_START:
3098     case GST_QT_MUX_MODE_ROBUST_RECORDING:
3099       atom_trak_add_samples (pad->trak, nsamples, (gint32) scaled_duration,
3100           sample_size, chunk_offset, sync, pts_offset);
3101       ret = gst_qt_mux_send_buffer (qtmux, buffer, &qtmux->mdat_size, TRUE);
3102       /* Check if it's time to re-write the headers in robust-recording mode */
3103       if (ret == GST_FLOW_OK
3104           && qtmux->mux_mode == GST_QT_MUX_MODE_ROBUST_RECORDING)
3105         ret = gst_qt_mux_robust_recording_update (qtmux, pad->total_duration);
3106       break;
3107     case GST_QT_MUX_MODE_FRAGMENTED:
3108     case GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE:
3109       /* ensure that always sync samples are marked as such */
3110       ret = gst_qt_mux_pad_fragment_add_buffer (qtmux, pad, buffer,
3111           is_last_buffer, nsamples, last_dts, (gint32) scaled_duration,
3112           sample_size, !pad->sync || sync, pts_offset);
3113       break;
3114   }
3115
3116   return ret;
3117 }
3118
3119 static GstFlowReturn
3120 gst_qt_mux_check_and_update_timecode (GstQTMux * qtmux, GstQTPad * pad,
3121     GstBuffer * buf, GstFlowReturn ret)
3122 {
3123   GstVideoTimeCodeMeta *tc_meta;
3124   GstVideoTimeCode *tc;
3125   GstBuffer *tc_buf;
3126   gsize szret;
3127   guint32 frames_since_daily_jam;
3128
3129   if (buf == NULL || (pad->tc_trak != NULL && pad->tc_pos == -1))
3130     return ret;
3131
3132   tc_meta = gst_buffer_get_video_time_code_meta (buf);
3133   if (!tc_meta)
3134     return ret;
3135
3136   tc = &tc_meta->tc;
3137
3138   /* This means we never got a timecode before */
3139   if (pad->first_tc == NULL) {
3140 #ifndef GST_DISABLE_GST_DEBUG
3141     gchar *tc_str = gst_video_time_code_to_string (tc);
3142     GST_DEBUG_OBJECT (qtmux, "Found first timecode %s", tc_str);
3143     g_free (tc_str);
3144 #endif
3145     g_assert (pad->tc_trak == NULL);
3146     tc_buf = gst_buffer_new_allocate (NULL, 4, NULL);
3147     pad->first_tc = gst_video_time_code_copy (tc);
3148     /* If frames are out of order, the frame we're currently getting might
3149      * not be the first one. Just write a 0 timecode for now and wait
3150      * until we receive a timecode that's lower than the current one */
3151     if (pad->is_out_of_order) {
3152       pad->first_pts = GST_BUFFER_PTS (buf);
3153       frames_since_daily_jam = 0;
3154       /* Position to rewrite */
3155       pad->tc_pos = qtmux->mdat_size;
3156     } else {
3157       frames_since_daily_jam =
3158           gst_video_time_code_frames_since_daily_jam (pad->first_tc);
3159       frames_since_daily_jam = GUINT32_TO_BE (frames_since_daily_jam);
3160     }
3161     /* Write the timecode trak now */
3162     pad->tc_trak = atom_trak_new (qtmux->context);
3163     atom_moov_add_trak (qtmux->moov, pad->tc_trak);
3164
3165     pad->trak->tref = atom_tref_new (FOURCC_tmcd);
3166     atom_tref_add_entry (pad->trak->tref, pad->tc_trak->tkhd.track_ID);
3167
3168     atom_trak_set_timecode_type (pad->tc_trak, qtmux->context, pad->first_tc);
3169
3170     szret = gst_buffer_fill (tc_buf, 0, &frames_since_daily_jam, 4);
3171     g_assert (szret == 4);
3172
3173     atom_trak_add_samples (pad->tc_trak, 1, 1, 4, qtmux->mdat_size, FALSE, 0);
3174     ret = gst_qt_mux_send_buffer (qtmux, tc_buf, &qtmux->mdat_size, TRUE);
3175
3176     /* Need to reset the current chunk (of the previous pad) here because
3177      * some other data was written now above, and the pad has to start a
3178      * new chunk now */
3179     qtmux->current_chunk_offset = -1;
3180     qtmux->current_chunk_size = 0;
3181     qtmux->current_chunk_duration = 0;
3182   } else if (pad->is_out_of_order) {
3183     /* Check for a lower timecode than the one stored */
3184     g_assert (pad->tc_trak != NULL);
3185     if (GST_BUFFER_DTS (buf) <= pad->first_pts) {
3186       if (gst_video_time_code_compare (tc, pad->first_tc) == -1) {
3187         gst_video_time_code_free (pad->first_tc);
3188         pad->first_tc = gst_video_time_code_copy (tc);
3189       }
3190     } else {
3191       guint64 bk_size = qtmux->mdat_size;
3192       GstSegment segment;
3193       /* If this frame's DTS is after the first PTS received, it means
3194        * we've already received the first frame to be presented. Otherwise
3195        * the decoder would need to go back in time */
3196       gst_qt_mux_update_timecode (qtmux, pad);
3197
3198       /* Reset writing position */
3199       gst_segment_init (&segment, GST_FORMAT_BYTES);
3200       segment.start = bk_size;
3201       gst_pad_push_event (qtmux->srcpad, gst_event_new_segment (&segment));
3202     }
3203   }
3204
3205   return ret;
3206 }
3207
3208 /*
3209  * Here we push the buffer and update the tables in the track atoms
3210  */
3211 static GstFlowReturn
3212 gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
3213 {
3214   GstBuffer *last_buf = NULL;
3215   GstClockTime duration;
3216   guint nsamples, sample_size;
3217   guint64 chunk_offset;
3218   gint64 last_dts, scaled_duration;
3219   gint64 pts_offset = 0;
3220   gboolean sync = FALSE;
3221   GstFlowReturn ret = GST_FLOW_OK;
3222
3223   if (!pad->fourcc)
3224     goto not_negotiated;
3225
3226   /* if this pad has a prepare function, call it */
3227   if (pad->prepare_buf_func != NULL) {
3228     buf = pad->prepare_buf_func (pad, buf, qtmux);
3229   }
3230
3231   ret = gst_qt_mux_check_and_update_timecode (qtmux, pad, buf, ret);
3232   if (ret != GST_FLOW_OK) {
3233     if (buf)
3234       gst_buffer_unref (buf);
3235     return ret;
3236   }
3237
3238   last_buf = pad->last_buf;
3239   pad->last_buf = buf;
3240
3241   if (last_buf == NULL) {
3242 #ifndef GST_DISABLE_GST_DEBUG
3243     if (buf == NULL) {
3244       GST_DEBUG_OBJECT (qtmux, "Pad %s has no previous buffer stored and "
3245           "received NULL buffer, doing nothing",
3246           GST_PAD_NAME (pad->collect.pad));
3247     } else {
3248       GST_LOG_OBJECT (qtmux,
3249           "Pad %s has no previous buffer stored, storing now",
3250           GST_PAD_NAME (pad->collect.pad));
3251     }
3252 #endif
3253     qtmux->current_pad = pad;
3254     goto exit;
3255   }
3256
3257   if (!GST_BUFFER_PTS_IS_VALID (last_buf))
3258     goto no_pts;
3259
3260   /* if this is the first buffer, store the timestamp */
3261   if (G_UNLIKELY (pad->first_ts == GST_CLOCK_TIME_NONE)) {
3262     if (GST_BUFFER_PTS_IS_VALID (last_buf)) {
3263       pad->first_ts = GST_BUFFER_PTS (last_buf);
3264     } else if (GST_BUFFER_DTS_IS_VALID (last_buf)) {
3265       pad->first_ts = GST_BUFFER_DTS (last_buf);
3266     }
3267
3268     if (GST_BUFFER_DTS_IS_VALID (last_buf)) {
3269       pad->first_dts = pad->last_dts = GST_BUFFER_DTS (last_buf);
3270     } else if (GST_BUFFER_PTS_IS_VALID (last_buf)) {
3271       pad->first_dts = pad->last_dts = GST_BUFFER_PTS (last_buf);
3272     }
3273
3274     if (GST_CLOCK_TIME_IS_VALID (pad->first_ts)) {
3275       GST_DEBUG ("setting first_ts to %" G_GUINT64_FORMAT, pad->first_ts);
3276     } else {
3277       GST_WARNING_OBJECT (qtmux, "First buffer for pad %s has no timestamp, "
3278           "using 0 as first timestamp", GST_PAD_NAME (pad->collect.pad));
3279       pad->first_ts = pad->first_dts = 0;
3280     }
3281     GST_DEBUG_OBJECT (qtmux, "Stored first timestamp for pad %s %"
3282         GST_TIME_FORMAT, GST_PAD_NAME (pad->collect.pad),
3283         GST_TIME_ARGS (pad->first_ts));
3284   }
3285
3286   if (buf && GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (buf)) &&
3287       GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (last_buf)) &&
3288       GST_BUFFER_DTS (buf) < GST_BUFFER_DTS (last_buf)) {
3289     GST_ERROR ("decreasing DTS value %" GST_TIME_FORMAT " < %" GST_TIME_FORMAT,
3290         GST_TIME_ARGS (GST_BUFFER_DTS (buf)),
3291         GST_TIME_ARGS (GST_BUFFER_DTS (last_buf)));
3292     pad->last_buf = buf = gst_buffer_make_writable (buf);
3293     GST_BUFFER_DTS (buf) = GST_BUFFER_DTS (last_buf);
3294   }
3295
3296   /* duration actually means time delta between samples, so we calculate
3297    * the duration based on the difference in DTS or PTS, falling back
3298    * to DURATION if the other two don't exist, such as with the last
3299    * sample before EOS. Or use 0 if nothing else is available */
3300   if (GST_BUFFER_DURATION_IS_VALID (last_buf))
3301     duration = GST_BUFFER_DURATION (last_buf);
3302   else
3303     duration = 0;
3304   if (!pad->sparse) {
3305     if (buf && GST_BUFFER_DTS_IS_VALID (buf)
3306         && GST_BUFFER_DTS_IS_VALID (last_buf))
3307       duration = GST_BUFFER_DTS (buf) - GST_BUFFER_DTS (last_buf);
3308     else if (buf && GST_BUFFER_PTS_IS_VALID (buf)
3309         && GST_BUFFER_PTS_IS_VALID (last_buf))
3310       duration = GST_BUFFER_PTS (buf) - GST_BUFFER_PTS (last_buf);
3311   }
3312
3313   if (qtmux->current_pad != pad || qtmux->current_chunk_offset == -1) {
3314     GST_DEBUG_OBJECT (qtmux,
3315         "Switching to next chunk for pad %s:%s: offset %" G_GUINT64_FORMAT
3316         ", size %" G_GUINT64_FORMAT ", duration %" GST_TIME_FORMAT,
3317         GST_DEBUG_PAD_NAME (pad->collect.pad), qtmux->current_chunk_offset,
3318         qtmux->current_chunk_size,
3319         GST_TIME_ARGS (qtmux->current_chunk_duration));
3320     qtmux->current_pad = pad;
3321     if (qtmux->current_chunk_offset == -1)
3322       qtmux->current_chunk_offset = qtmux->mdat_size;
3323     else
3324       qtmux->current_chunk_offset += qtmux->current_chunk_size;
3325     qtmux->current_chunk_size = 0;
3326     qtmux->current_chunk_duration = 0;
3327   }
3328
3329   last_dts = gst_util_uint64_scale_round (pad->last_dts,
3330       atom_trak_get_timescale (pad->trak), GST_SECOND);
3331
3332   /* fragments only deal with 1 buffer == 1 chunk (== 1 sample) */
3333   if (pad->sample_size && !qtmux->fragment_sequence) {
3334     GstClockTime expected_timestamp;
3335
3336     /* Constant size packets: usually raw audio (with many samples per
3337        buffer (= chunk)), but can also be fixed-packet-size codecs like ADPCM
3338      */
3339     sample_size = pad->sample_size;
3340     if (gst_buffer_get_size (last_buf) % sample_size != 0)
3341       goto fragmented_sample;
3342
3343     /* note: qt raw audio storage warps it implicitly into a timewise
3344      * perfect stream, discarding buffer times.
3345      * If the difference between the current PTS and the expected one
3346      * becomes too big, we error out: there was a gap and we have no way to
3347      * represent that, causing A/V sync to be off */
3348     expected_timestamp =
3349         gst_util_uint64_scale (pad->sample_offset, GST_SECOND,
3350         atom_trak_get_timescale (pad->trak)) + pad->first_ts;
3351     if (ABSDIFF (GST_BUFFER_DTS_OR_PTS (last_buf),
3352             expected_timestamp) > qtmux->max_raw_audio_drift)
3353       goto raw_audio_timestamp_drift;
3354
3355     if (GST_BUFFER_DURATION (last_buf) != GST_CLOCK_TIME_NONE) {
3356       nsamples = gst_util_uint64_scale_round (GST_BUFFER_DURATION (last_buf),
3357           atom_trak_get_timescale (pad->trak), GST_SECOND);
3358       duration = GST_BUFFER_DURATION (last_buf);
3359     } else {
3360       nsamples = gst_buffer_get_size (last_buf) / sample_size;
3361       duration =
3362           gst_util_uint64_scale_round (nsamples, GST_SECOND,
3363           atom_trak_get_timescale (pad->trak));
3364     }
3365
3366     /* timescale = samplerate */
3367     scaled_duration = 1;
3368     pad->last_dts =
3369         pad->first_dts + gst_util_uint64_scale_round (pad->sample_offset +
3370         nsamples, GST_SECOND, atom_trak_get_timescale (pad->trak));
3371   } else {
3372     nsamples = 1;
3373     sample_size = gst_buffer_get_size (last_buf);
3374     if ((buf && GST_BUFFER_DTS_IS_VALID (buf))
3375         || GST_BUFFER_DTS_IS_VALID (last_buf)) {
3376       gint64 scaled_dts;
3377       if (buf && GST_BUFFER_DTS_IS_VALID (buf)) {
3378         pad->last_dts = GST_BUFFER_DTS (buf);
3379       } else {
3380         pad->last_dts = GST_BUFFER_DTS (last_buf) + duration;
3381       }
3382       if ((gint64) (pad->last_dts) < 0) {
3383         scaled_dts = -gst_util_uint64_scale_round (-pad->last_dts,
3384             atom_trak_get_timescale (pad->trak), GST_SECOND);
3385       } else {
3386         scaled_dts = gst_util_uint64_scale_round (pad->last_dts,
3387             atom_trak_get_timescale (pad->trak), GST_SECOND);
3388       }
3389       scaled_duration = scaled_dts - last_dts;
3390       last_dts = scaled_dts;
3391     } else {
3392       /* first convert intended timestamp (in GstClockTime resolution) to
3393        * trak timescale, then derive delta;
3394        * this ensures sums of (scale)delta add up to converted timestamp,
3395        * which only deviates at most 1/scale from timestamp itself */
3396       scaled_duration = gst_util_uint64_scale_round (pad->last_dts + duration,
3397           atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts;
3398       pad->last_dts += duration;
3399     }
3400   }
3401
3402   pad->sample_offset += nsamples;
3403
3404   /* for computing the avg bitrate */
3405   pad->total_bytes += gst_buffer_get_size (last_buf);
3406   pad->total_duration += duration;
3407   qtmux->current_chunk_size += gst_buffer_get_size (last_buf);
3408   qtmux->current_chunk_duration += duration;
3409
3410   chunk_offset = qtmux->current_chunk_offset;
3411
3412   GST_LOG_OBJECT (qtmux,
3413       "Pad (%s) dts updated to %" GST_TIME_FORMAT,
3414       GST_PAD_NAME (pad->collect.pad), GST_TIME_ARGS (pad->last_dts));
3415   GST_LOG_OBJECT (qtmux,
3416       "Adding %d samples to track, duration: %" G_GUINT64_FORMAT
3417       " size: %" G_GUINT32_FORMAT " chunk offset: %" G_GUINT64_FORMAT,
3418       nsamples, scaled_duration, sample_size, chunk_offset);
3419
3420   /* might be a sync sample */
3421   if (pad->sync &&
3422       !GST_BUFFER_FLAG_IS_SET (last_buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
3423     GST_LOG_OBJECT (qtmux, "Adding new sync sample entry for track of pad %s",
3424         GST_PAD_NAME (pad->collect.pad));
3425     sync = TRUE;
3426   }
3427
3428   if (GST_BUFFER_DTS_IS_VALID (last_buf)) {
3429     last_dts = gst_util_uint64_scale_round (GST_BUFFER_DTS (last_buf),
3430         atom_trak_get_timescale (pad->trak), GST_SECOND);
3431     pts_offset =
3432         (gint64) (gst_util_uint64_scale_round (GST_BUFFER_PTS (last_buf),
3433             atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts);
3434   } else {
3435     pts_offset = 0;
3436     last_dts = gst_util_uint64_scale_round (GST_BUFFER_PTS (last_buf),
3437         atom_trak_get_timescale (pad->trak), GST_SECOND);
3438   }
3439   GST_DEBUG ("dts: %" GST_TIME_FORMAT " pts: %" GST_TIME_FORMAT
3440       " timebase_dts: %d pts_offset: %d",
3441       GST_TIME_ARGS (GST_BUFFER_DTS (last_buf)),
3442       GST_TIME_ARGS (GST_BUFFER_PTS (last_buf)),
3443       (int) (last_dts), (int) (pts_offset));
3444
3445   if (GST_CLOCK_TIME_IS_VALID (duration)
3446       && (qtmux->current_chunk_duration > qtmux->longest_chunk
3447           || !GST_CLOCK_TIME_IS_VALID (qtmux->longest_chunk))) {
3448     GST_DEBUG_OBJECT (qtmux,
3449         "New longest chunk found: %" GST_TIME_FORMAT ", pad %s",
3450         GST_TIME_ARGS (qtmux->current_chunk_duration),
3451         GST_PAD_NAME (pad->collect.pad));
3452     qtmux->longest_chunk = qtmux->current_chunk_duration;
3453   }
3454
3455   /* now we go and register this buffer/sample all over */
3456   ret = gst_qt_mux_register_and_push_sample (qtmux, pad, last_buf,
3457       buf == NULL, nsamples, last_dts, scaled_duration, sample_size,
3458       chunk_offset, sync, TRUE, pts_offset);
3459
3460   /* if this is sparse and we have a next buffer, check if there is any gap
3461    * between them to insert an empty sample */
3462   if (pad->sparse && buf) {
3463     if (pad->create_empty_buffer) {
3464       GstBuffer *empty_buf;
3465       gint64 empty_duration =
3466           GST_BUFFER_PTS (buf) - (GST_BUFFER_PTS (last_buf) + duration);
3467       gint64 empty_duration_scaled;
3468
3469       empty_buf = pad->create_empty_buffer (pad, empty_duration);
3470
3471       empty_duration_scaled = gst_util_uint64_scale_round (empty_duration,
3472           atom_trak_get_timescale (pad->trak), GST_SECOND);
3473
3474       pad->total_bytes += gst_buffer_get_size (empty_buf);
3475       pad->total_duration += duration;
3476
3477       ret =
3478           gst_qt_mux_register_and_push_sample (qtmux, pad, empty_buf, FALSE, 1,
3479           last_dts + scaled_duration, empty_duration_scaled,
3480           gst_buffer_get_size (empty_buf), chunk_offset, sync, TRUE, 0);
3481     } else {
3482       /* our only case currently is tx3g subtitles, so there is no reason to fill this yet */
3483       g_assert_not_reached ();
3484       GST_WARNING_OBJECT (qtmux,
3485           "no empty buffer creation function found for pad %s",
3486           GST_PAD_NAME (pad->collect.pad));
3487     }
3488   }
3489
3490 exit:
3491
3492   return ret;
3493
3494   /* ERRORS */
3495 bail:
3496   {
3497     gst_buffer_unref (last_buf);
3498     return GST_FLOW_ERROR;
3499   }
3500 fragmented_sample:
3501   {
3502     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
3503         ("Audio buffer contains fragmented sample."));
3504     goto bail;
3505   }
3506 raw_audio_timestamp_drift:
3507   {
3508     /* TODO: Could in theory be implemented with edit lists */
3509     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
3510         ("Audio stream timestamps are drifting (got %" GST_TIME_FORMAT
3511             ", expected %" GST_TIME_FORMAT "). This is not supported yet!",
3512             GST_TIME_ARGS (GST_BUFFER_DTS_OR_PTS (last_buf)),
3513             GST_TIME_ARGS (gst_util_uint64_scale (pad->sample_offset,
3514                     GST_SECOND,
3515                     atom_trak_get_timescale (pad->trak)) + pad->first_ts)));
3516     goto bail;
3517   }
3518 no_pts:
3519   {
3520     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), ("Buffer has no PTS."));
3521     goto bail;
3522   }
3523 not_negotiated:
3524   {
3525     GST_ELEMENT_ERROR (qtmux, CORE, NEGOTIATION, (NULL),
3526         ("format wasn't negotiated before buffer flow on pad %s",
3527             GST_PAD_NAME (pad->collect.pad)));
3528     if (buf)
3529       gst_buffer_unref (buf);
3530     return GST_FLOW_NOT_NEGOTIATED;
3531   }
3532 }
3533
3534 /*
3535  * DTS running time can be negative. There is no way to represent that in
3536  * MP4 however, thus we need to offset DTS so that it starts from 0.
3537  */
3538 static void
3539 gst_qt_pad_adjust_buffer_dts (GstQTMux * qtmux, GstQTPad * pad,
3540     GstCollectData * cdata, GstBuffer ** buf)
3541 {
3542   GstClockTime pts;
3543   gint64 dts;
3544
3545   pts = GST_BUFFER_PTS (*buf);
3546   dts = GST_COLLECT_PADS_DTS (cdata);
3547
3548   GST_LOG_OBJECT (qtmux, "selected pad %s with PTS %" GST_TIME_FORMAT
3549       " and DTS %" GST_STIME_FORMAT, GST_PAD_NAME (cdata->pad),
3550       GST_TIME_ARGS (pts), GST_STIME_ARGS (dts));
3551
3552   if (!GST_CLOCK_TIME_IS_VALID (pad->dts_adjustment)) {
3553     if (GST_CLOCK_STIME_IS_VALID (dts) && dts < 0)
3554       pad->dts_adjustment = -dts;
3555     else
3556       pad->dts_adjustment = 0;
3557   }
3558
3559   if (pad->dts_adjustment > 0) {
3560     *buf = gst_buffer_make_writable (*buf);
3561
3562     dts += pad->dts_adjustment;
3563
3564     if (GST_CLOCK_TIME_IS_VALID (pts))
3565       pts += pad->dts_adjustment;
3566
3567     if (GST_CLOCK_STIME_IS_VALID (dts) && dts < 0) {
3568       GST_WARNING_OBJECT (pad, "Decreasing DTS.");
3569       dts = 0;
3570     }
3571
3572     if (pts < dts) {
3573       GST_WARNING_OBJECT (pad, "DTS is bigger then PTS");
3574       pts = dts;
3575     }
3576
3577     GST_BUFFER_PTS (*buf) = pts;
3578     GST_BUFFER_DTS (*buf) = dts;
3579
3580     GST_LOG_OBJECT (qtmux, "time adjusted to PTS %" GST_TIME_FORMAT
3581         " and DTS %" GST_TIME_FORMAT, GST_TIME_ARGS (pts), GST_TIME_ARGS (dts));
3582   }
3583 }
3584
3585 static GstQTPad *
3586 find_best_pad (GstQTMux * qtmux, GstCollectPads * pads)
3587 {
3588   GSList *walk;
3589   GstQTPad *best_pad = NULL;
3590
3591   if (qtmux->current_pad &&
3592       (qtmux->interleave_bytes != 0 || qtmux->interleave_time != 0) &&
3593       (qtmux->interleave_bytes == 0
3594           || qtmux->current_chunk_size <= qtmux->interleave_bytes)
3595       && (qtmux->interleave_time == 0
3596           || qtmux->current_chunk_duration <= qtmux->interleave_time)
3597       && qtmux->mux_mode != GST_QT_MUX_MODE_FRAGMENTED
3598       && qtmux->mux_mode != GST_QT_MUX_MODE_FRAGMENTED_STREAMABLE) {
3599     GstBuffer *tmp_buf =
3600         gst_collect_pads_peek (pads, (GstCollectData *) qtmux->current_pad);
3601
3602     if (tmp_buf || qtmux->current_pad->last_buf) {
3603       best_pad = qtmux->current_pad;
3604       if (tmp_buf)
3605         gst_buffer_unref (tmp_buf);
3606       GST_DEBUG_OBJECT (qtmux, "Reusing pad %s:%s",
3607           GST_DEBUG_PAD_NAME (best_pad->collect.pad));
3608     }
3609   } else if (qtmux->collect->data->next) {
3610     /* Only switch pads if we have more than one, otherwise
3611      * we can just put everything into a single chunk and save
3612      * a few bytes of offsets
3613      */
3614     if (qtmux->current_pad)
3615       GST_DEBUG_OBJECT (qtmux, "Switching from pad %s:%s",
3616           GST_DEBUG_PAD_NAME (qtmux->current_pad->collect.pad));
3617     best_pad = qtmux->current_pad = NULL;
3618   }
3619
3620   if (!best_pad) {
3621     GstClockTime best_time = GST_CLOCK_TIME_NONE;
3622
3623     for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
3624       GstCollectData *cdata = (GstCollectData *) walk->data;
3625       GstQTPad *qtpad = (GstQTPad *) cdata;
3626       GstBuffer *tmp_buf;
3627       GstClockTime timestamp;
3628
3629       tmp_buf = gst_collect_pads_peek (pads, cdata);
3630       if (!tmp_buf) {
3631         /* This one is newly EOS now, finish it for real */
3632         if (qtpad->last_buf) {
3633           timestamp = GST_BUFFER_DTS_OR_PTS (qtpad->last_buf);
3634         } else {
3635           continue;
3636         }
3637       } else {
3638         if (qtpad->last_buf)
3639           timestamp = GST_BUFFER_DTS_OR_PTS (qtpad->last_buf);
3640         else
3641           timestamp = GST_BUFFER_DTS_OR_PTS (tmp_buf);
3642       }
3643
3644       if (best_pad == NULL ||
3645           !GST_CLOCK_TIME_IS_VALID (best_time) || timestamp < best_time) {
3646         best_pad = qtpad;
3647         best_time = timestamp;
3648       }
3649
3650       if (tmp_buf)
3651         gst_buffer_unref (tmp_buf);
3652     }
3653
3654     if (best_pad) {
3655       GST_DEBUG_OBJECT (qtmux, "Choosing pad %s:%s",
3656           GST_DEBUG_PAD_NAME (best_pad->collect.pad));
3657     } else {
3658       GST_DEBUG_OBJECT (qtmux, "No best pad: EOS");
3659     }
3660   }
3661
3662   return best_pad;
3663 }
3664
3665 static GstFlowReturn
3666 gst_qt_mux_collected (GstCollectPads * pads, gpointer user_data)
3667 {
3668   GstFlowReturn ret = GST_FLOW_OK;
3669   GstQTMux *qtmux = GST_QT_MUX_CAST (user_data);
3670   GstQTPad *best_pad = NULL;
3671
3672   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_STARTED)) {
3673     if ((ret = gst_qt_mux_start_file (qtmux)) != GST_FLOW_OK)
3674       return ret;
3675
3676     qtmux->state = GST_QT_MUX_STATE_DATA;
3677   }
3678
3679   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS))
3680     return GST_FLOW_EOS;
3681
3682   best_pad = find_best_pad (qtmux, pads);
3683
3684   /* clipping already converted to running time */
3685   if (best_pad != NULL) {
3686     GstBuffer *buf = gst_collect_pads_pop (pads, (GstCollectData *) best_pad);
3687
3688     g_assert (buf || best_pad->last_buf);
3689     if (buf)
3690       gst_qt_pad_adjust_buffer_dts (qtmux, best_pad,
3691           (GstCollectData *) best_pad, &buf);
3692
3693     ret = gst_qt_mux_add_buffer (qtmux, best_pad, buf);
3694   } else {
3695     qtmux->state = GST_QT_MUX_STATE_EOS;
3696     ret = gst_qt_mux_stop_file (qtmux);
3697     if (ret == GST_FLOW_OK) {
3698       GST_DEBUG_OBJECT (qtmux, "Pushing eos");
3699       gst_pad_push_event (qtmux->srcpad, gst_event_new_eos ());
3700       ret = GST_FLOW_EOS;
3701     } else {
3702       GST_WARNING_OBJECT (qtmux, "Failed to stop file: %s",
3703           gst_flow_get_name (ret));
3704     }
3705   }
3706
3707   return ret;
3708 }
3709
3710 static gboolean
3711 check_field (GQuark field_id, const GValue * value, gpointer user_data)
3712 {
3713   GstStructure *structure = (GstStructure *) user_data;
3714   const GValue *other = gst_structure_id_get_value (structure, field_id);
3715   if (other == NULL)
3716     return FALSE;
3717   return gst_value_compare (value, other) == GST_VALUE_EQUAL;
3718 }
3719
3720 static gboolean
3721 gst_qtmux_caps_is_subset_full (GstQTMux * qtmux, GstCaps * subset,
3722     GstCaps * superset)
3723 {
3724   GstStructure *sub_s = gst_caps_get_structure (subset, 0);
3725   GstStructure *sup_s = gst_caps_get_structure (superset, 0);
3726
3727   return gst_structure_foreach (sub_s, check_field, sup_s);
3728 }
3729
3730 static gboolean
3731 gst_qt_mux_audio_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
3732 {
3733   GstPad *pad = qtpad->collect.pad;
3734   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
3735   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
3736   GstStructure *structure;
3737   const gchar *mimetype;
3738   gint rate, channels;
3739   const GValue *value = NULL;
3740   const GstBuffer *codec_data = NULL;
3741   GstQTMuxFormat format;
3742   AudioSampleEntry entry = { 0, };
3743   AtomInfo *ext_atom = NULL;
3744   gint constant_size = 0;
3745   const gchar *stream_format;
3746
3747   qtpad->prepare_buf_func = NULL;
3748
3749   /* does not go well to renegotiate stream mid-way, unless
3750    * the old caps are a subset of the new one (this means upstream
3751    * added more info to the caps, as both should be 'fixed' caps) */
3752   if (qtpad->fourcc) {
3753     GstCaps *current_caps;
3754
3755     current_caps = gst_pad_get_current_caps (pad);
3756     g_assert (caps != NULL);
3757
3758     if (!gst_qtmux_caps_is_subset_full (qtmux, current_caps, caps)) {
3759       gst_caps_unref (current_caps);
3760       goto refuse_renegotiation;
3761     }
3762     GST_DEBUG_OBJECT (qtmux,
3763         "pad %s accepted renegotiation to %" GST_PTR_FORMAT " from %"
3764         GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, current_caps);
3765     gst_caps_unref (current_caps);
3766   }
3767
3768   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
3769       GST_DEBUG_PAD_NAME (pad), caps);
3770
3771   format = qtmux_klass->format;
3772   structure = gst_caps_get_structure (caps, 0);
3773   mimetype = gst_structure_get_name (structure);
3774
3775   /* common info */
3776   if (!gst_structure_get_int (structure, "channels", &channels) ||
3777       !gst_structure_get_int (structure, "rate", &rate)) {
3778     goto refuse_caps;
3779   }
3780
3781   /* optional */
3782   value = gst_structure_get_value (structure, "codec_data");
3783   if (value != NULL)
3784     codec_data = gst_value_get_buffer (value);
3785
3786   qtpad->is_out_of_order = FALSE;
3787
3788   /* set common properties */
3789   entry.sample_rate = rate;
3790   entry.channels = channels;
3791   /* default */
3792   entry.sample_size = 16;
3793   /* this is the typical compressed case */
3794   if (format == GST_QT_MUX_FORMAT_QT) {
3795     entry.version = 1;
3796     entry.compression_id = -2;
3797   }
3798
3799   /* now map onto a fourcc, and some extra properties */
3800   if (strcmp (mimetype, "audio/mpeg") == 0) {
3801     gint mpegversion = 0, mpegaudioversion = 0;
3802     gint layer = -1;
3803
3804     gst_structure_get_int (structure, "mpegversion", &mpegversion);
3805     switch (mpegversion) {
3806       case 1:
3807         gst_structure_get_int (structure, "layer", &layer);
3808         gst_structure_get_int (structure, "mpegaudioversion",
3809             &mpegaudioversion);
3810
3811         /* mp1/2/3 */
3812         /* note: QuickTime player does not like mp3 either way in iso/mp4 */
3813         if (format == GST_QT_MUX_FORMAT_QT)
3814           entry.fourcc = FOURCC__mp3;
3815         else {
3816           entry.fourcc = FOURCC_mp4a;
3817           ext_atom =
3818               build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG1_P3,
3819               ESDS_STREAM_TYPE_AUDIO, codec_data, qtpad->avg_bitrate,
3820               qtpad->max_bitrate);
3821         }
3822         if (layer == 1) {
3823           g_warn_if_fail (format == GST_QT_MUX_FORMAT_MP4);
3824           entry.samples_per_packet = 384;
3825         } else if (layer == 2) {
3826           g_warn_if_fail (format == GST_QT_MUX_FORMAT_MP4);
3827           entry.samples_per_packet = 1152;
3828         } else {
3829           g_warn_if_fail (layer == 3);
3830           entry.samples_per_packet = (mpegaudioversion <= 1) ? 1152 : 576;
3831         }
3832         entry.bytes_per_sample = 2;
3833         break;
3834       case 4:
3835
3836         /* check stream-format */
3837         stream_format = gst_structure_get_string (structure, "stream-format");
3838         if (stream_format) {
3839           if (strcmp (stream_format, "raw") != 0) {
3840             GST_WARNING_OBJECT (qtmux, "Unsupported AAC stream-format %s, "
3841                 "please use 'raw'", stream_format);
3842             goto refuse_caps;
3843           }
3844         } else {
3845           GST_WARNING_OBJECT (qtmux, "No stream-format present in caps, "
3846               "assuming 'raw'");
3847         }
3848
3849         if (!codec_data || gst_buffer_get_size ((GstBuffer *) codec_data) < 2) {
3850           GST_WARNING_OBJECT (qtmux, "no (valid) codec_data for AAC audio");
3851           goto refuse_caps;
3852         } else {
3853           guint8 profile;
3854
3855           gst_buffer_extract ((GstBuffer *) codec_data, 0, &profile, 1);
3856           /* warn if not Low Complexity profile */
3857           profile >>= 3;
3858           if (profile != 2)
3859             GST_WARNING_OBJECT (qtmux,
3860                 "non-LC AAC may not run well on (Apple) QuickTime/iTunes");
3861         }
3862
3863         /* AAC */
3864         entry.fourcc = FOURCC_mp4a;
3865
3866         if (format == GST_QT_MUX_FORMAT_QT)
3867           ext_atom = build_mov_aac_extension (qtpad->trak, codec_data,
3868               qtpad->avg_bitrate, qtpad->max_bitrate);
3869         else
3870           ext_atom =
3871               build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P3,
3872               ESDS_STREAM_TYPE_AUDIO, codec_data, qtpad->avg_bitrate,
3873               qtpad->max_bitrate);
3874         break;
3875       default:
3876         break;
3877     }
3878   } else if (strcmp (mimetype, "audio/AMR") == 0) {
3879     entry.fourcc = FOURCC_samr;
3880     entry.sample_size = 16;
3881     entry.samples_per_packet = 160;
3882     entry.bytes_per_sample = 2;
3883     ext_atom = build_amr_extension ();
3884   } else if (strcmp (mimetype, "audio/AMR-WB") == 0) {
3885     entry.fourcc = FOURCC_sawb;
3886     entry.sample_size = 16;
3887     entry.samples_per_packet = 320;
3888     entry.bytes_per_sample = 2;
3889     ext_atom = build_amr_extension ();
3890   } else if (strcmp (mimetype, "audio/x-raw") == 0) {
3891     GstAudioInfo info;
3892
3893     gst_audio_info_init (&info);
3894     if (!gst_audio_info_from_caps (&info, caps))
3895       goto refuse_caps;
3896
3897     /* spec has no place for a distinction in these */
3898     if (info.finfo->width != info.finfo->depth) {
3899       GST_DEBUG_OBJECT (qtmux, "width must be same as depth!");
3900       goto refuse_caps;
3901     }
3902
3903     if ((info.finfo->flags & GST_AUDIO_FORMAT_FLAG_SIGNED)) {
3904       if (info.finfo->endianness == G_LITTLE_ENDIAN)
3905         entry.fourcc = FOURCC_sowt;
3906       else if (info.finfo->endianness == G_BIG_ENDIAN)
3907         entry.fourcc = FOURCC_twos;
3908       else
3909         entry.fourcc = FOURCC_sowt;
3910       /* maximum backward compatibility; only new version for > 16 bit */
3911       if (info.finfo->depth <= 16)
3912         entry.version = 0;
3913       /* not compressed in any case */
3914       entry.compression_id = 0;
3915       /* QT spec says: max at 16 bit even if sample size were actually larger,
3916        * however, most players (e.g. QuickTime!) seem to disagree, so ... */
3917       entry.sample_size = info.finfo->depth;
3918       entry.bytes_per_sample = info.finfo->depth / 8;
3919       entry.samples_per_packet = 1;
3920       entry.bytes_per_packet = info.finfo->depth / 8;
3921       entry.bytes_per_frame = entry.bytes_per_packet * info.channels;
3922     } else {
3923       if (info.finfo->width == 8 && info.finfo->depth == 8) {
3924         /* fall back to old 8-bit version */
3925         entry.fourcc = FOURCC_raw_;
3926         entry.version = 0;
3927         entry.compression_id = 0;
3928         entry.sample_size = 8;
3929       } else {
3930         GST_DEBUG_OBJECT (qtmux, "non 8-bit PCM must be signed");
3931         goto refuse_caps;
3932       }
3933     }
3934     constant_size = (info.finfo->depth / 8) * info.channels;
3935   } else if (strcmp (mimetype, "audio/x-alaw") == 0) {
3936     entry.fourcc = FOURCC_alaw;
3937     entry.samples_per_packet = 1023;
3938     entry.bytes_per_sample = 2;
3939   } else if (strcmp (mimetype, "audio/x-mulaw") == 0) {
3940     entry.fourcc = FOURCC_ulaw;
3941     entry.samples_per_packet = 1023;
3942     entry.bytes_per_sample = 2;
3943   } else if (strcmp (mimetype, "audio/x-adpcm") == 0) {
3944     gint blocksize;
3945     if (!gst_structure_get_int (structure, "block_align", &blocksize)) {
3946       GST_DEBUG_OBJECT (qtmux, "broken caps, block_align missing");
3947       goto refuse_caps;
3948     }
3949     /* Currently only supports WAV-style IMA ADPCM, for which the codec id is
3950        0x11 */
3951     entry.fourcc = MS_WAVE_FOURCC (0x11);
3952     /* 4 byte header per channel (including one sample). 2 samples per byte
3953        remaining. Simplifying gives the following (samples per block per
3954        channel) */
3955     entry.samples_per_packet = 2 * blocksize / channels - 7;
3956     entry.bytes_per_sample = 2;
3957
3958     entry.bytes_per_frame = blocksize;
3959     entry.bytes_per_packet = blocksize / channels;
3960     /* ADPCM has constant size packets */
3961     constant_size = 1;
3962     /* TODO: I don't really understand why this helps, but it does! Constant
3963      * size and compression_id of -2 seem to be incompatible, and other files
3964      * in the wild use this too. */
3965     entry.compression_id = -1;
3966
3967     ext_atom = build_ima_adpcm_extension (channels, rate, blocksize);
3968   } else if (strcmp (mimetype, "audio/x-alac") == 0) {
3969     GstBuffer *codec_config;
3970     gint len;
3971     GstMapInfo map;
3972
3973     entry.fourcc = FOURCC_alac;
3974     gst_buffer_map ((GstBuffer *) codec_data, &map, GST_MAP_READ);
3975     /* let's check if codec data already comes with 'alac' atom prefix */
3976     if (!codec_data || (len = map.size) < 28) {
3977       GST_DEBUG_OBJECT (qtmux, "broken caps, codec data missing");
3978       gst_buffer_unmap ((GstBuffer *) codec_data, &map);
3979       goto refuse_caps;
3980     }
3981     if (GST_READ_UINT32_LE (map.data + 4) == FOURCC_alac) {
3982       len -= 8;
3983       codec_config =
3984           gst_buffer_copy_region ((GstBuffer *) codec_data,
3985           GST_BUFFER_COPY_MEMORY, 8, len);
3986     } else {
3987       codec_config = gst_buffer_ref ((GstBuffer *) codec_data);
3988     }
3989     gst_buffer_unmap ((GstBuffer *) codec_data, &map);
3990     if (len != 28) {
3991       /* does not look good, but perhaps some trailing unneeded stuff */
3992       GST_WARNING_OBJECT (qtmux, "unexpected codec-data size, possibly broken");
3993     }
3994     if (format == GST_QT_MUX_FORMAT_QT)
3995       ext_atom = build_mov_alac_extension (codec_config);
3996     else
3997       ext_atom = build_codec_data_extension (FOURCC_alac, codec_config);
3998     /* set some more info */
3999     gst_buffer_map (codec_config, &map, GST_MAP_READ);
4000     entry.bytes_per_sample = 2;
4001     entry.samples_per_packet = GST_READ_UINT32_BE (map.data + 4);
4002     gst_buffer_unmap (codec_config, &map);
4003     gst_buffer_unref (codec_config);
4004   } else if (strcmp (mimetype, "audio/x-ac3") == 0) {
4005     entry.fourcc = FOURCC_ac_3;
4006
4007     /* Fixed values according to TS 102 366 but it also mentions that
4008      * they should be ignored */
4009     entry.channels = 2;
4010     entry.sample_size = 16;
4011
4012     /* AC-3 needs an extension atom but its data can only be obtained from
4013      * the stream itself. Abuse the prepare_buf_func so we parse a frame
4014      * and get the needed data */
4015     qtpad->prepare_buf_func = gst_qt_mux_prepare_parse_ac3_frame;
4016   } else if (strcmp (mimetype, "audio/x-opus") == 0) {
4017     /* Based on the specification defined in:
4018      * https://www.opus-codec.org/docs/opus_in_isobmff.html */
4019     guint8 channels, mapping_family, stream_count, coupled_count;
4020     guint16 pre_skip;
4021     gint16 output_gain;
4022     guint32 rate;
4023     guint8 channel_mapping[256];
4024     const GValue *streamheader;
4025     const GValue *first_element;
4026     GstBuffer *header;
4027
4028     entry.fourcc = FOURCC_opus;
4029     entry.sample_size = 16;
4030
4031     streamheader = gst_structure_get_value (structure, "streamheader");
4032     if (streamheader && GST_VALUE_HOLDS_ARRAY (streamheader) &&
4033         gst_value_array_get_size (streamheader) != 0) {
4034       first_element = gst_value_array_get_value (streamheader, 0);
4035       header = gst_value_get_buffer (first_element);
4036       if (!gst_codec_utils_opus_parse_header (header, &rate, &channels,
4037               &mapping_family, &stream_count, &coupled_count, channel_mapping,
4038               &pre_skip, &output_gain)) {
4039         GST_ERROR_OBJECT (qtmux, "Incomplete OpusHead");
4040         goto refuse_caps;
4041       }
4042     } else {
4043       GST_WARNING_OBJECT (qtmux,
4044           "no streamheader field in caps %" GST_PTR_FORMAT, caps);
4045
4046       if (!gst_codec_utils_opus_parse_caps (caps, &rate, &channels,
4047               &mapping_family, &stream_count, &coupled_count,
4048               channel_mapping)) {
4049         GST_ERROR_OBJECT (qtmux, "Incomplete Opus caps");
4050         goto refuse_caps;
4051       }
4052       pre_skip = 0;
4053       output_gain = 0;
4054     }
4055
4056     entry.channels = channels;
4057     ext_atom = build_opus_extension (rate, channels, mapping_family,
4058         stream_count, coupled_count, channel_mapping, pre_skip, output_gain);
4059   }
4060
4061   if (!entry.fourcc)
4062     goto refuse_caps;
4063
4064   /* ok, set the pad info accordingly */
4065   qtpad->fourcc = entry.fourcc;
4066   qtpad->sample_size = constant_size;
4067   qtpad->trak_ste =
4068       (SampleTableEntry *) atom_trak_set_audio_type (qtpad->trak,
4069       qtmux->context, &entry,
4070       qtmux->trak_timescale ? qtmux->trak_timescale : entry.sample_rate,
4071       ext_atom, constant_size);
4072
4073   gst_object_unref (qtmux);
4074   return TRUE;
4075
4076   /* ERRORS */
4077 refuse_caps:
4078   {
4079     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
4080         GST_PAD_NAME (pad), caps);
4081     gst_object_unref (qtmux);
4082     return FALSE;
4083   }
4084 refuse_renegotiation:
4085   {
4086     GST_WARNING_OBJECT (qtmux,
4087         "pad %s refused renegotiation to %" GST_PTR_FORMAT,
4088         GST_PAD_NAME (pad), caps);
4089     gst_object_unref (qtmux);
4090     return FALSE;
4091   }
4092 }
4093
4094 static gboolean
4095 gst_qt_mux_video_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
4096 {
4097   GstPad *pad = qtpad->collect.pad;
4098   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
4099   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
4100   GstStructure *structure;
4101   const gchar *mimetype;
4102   gint width, height, depth = -1;
4103   gint framerate_num, framerate_den;
4104   guint32 rate;
4105   const GValue *value = NULL;
4106   const GstBuffer *codec_data = NULL;
4107   VisualSampleEntry entry = { 0, };
4108   GstQTMuxFormat format;
4109   AtomInfo *ext_atom = NULL;
4110   GList *ext_atom_list = NULL;
4111   gboolean sync = FALSE;
4112   int par_num, par_den;
4113
4114   qtpad->prepare_buf_func = NULL;
4115
4116   /* does not go well to renegotiate stream mid-way, unless
4117    * the old caps are a subset of the new one (this means upstream
4118    * added more info to the caps, as both should be 'fixed' caps) */
4119   if (qtpad->fourcc) {
4120     GstCaps *current_caps;
4121
4122     current_caps = gst_pad_get_current_caps (pad);
4123     g_assert (caps != NULL);
4124
4125     if (!gst_qtmux_caps_is_subset_full (qtmux, current_caps, caps)) {
4126       gst_caps_unref (current_caps);
4127       goto refuse_renegotiation;
4128     }
4129     GST_DEBUG_OBJECT (qtmux,
4130         "pad %s accepted renegotiation to %" GST_PTR_FORMAT " from %"
4131         GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, current_caps);
4132     gst_caps_unref (current_caps);
4133   }
4134
4135   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
4136       GST_DEBUG_PAD_NAME (pad), caps);
4137
4138   format = qtmux_klass->format;
4139   structure = gst_caps_get_structure (caps, 0);
4140   mimetype = gst_structure_get_name (structure);
4141
4142   /* required parts */
4143   if (!gst_structure_get_int (structure, "width", &width) ||
4144       !gst_structure_get_int (structure, "height", &height))
4145     goto refuse_caps;
4146
4147   /* optional */
4148   depth = -1;
4149   /* works as a default timebase */
4150   framerate_num = 10000;
4151   framerate_den = 1;
4152   gst_structure_get_fraction (structure, "framerate", &framerate_num,
4153       &framerate_den);
4154   gst_structure_get_int (structure, "depth", &depth);
4155   value = gst_structure_get_value (structure, "codec_data");
4156   if (value != NULL)
4157     codec_data = gst_value_get_buffer (value);
4158
4159   par_num = 1;
4160   par_den = 1;
4161   gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_num,
4162       &par_den);
4163
4164   qtpad->is_out_of_order = FALSE;
4165
4166   /* bring frame numerator into a range that ensures both reasonable resolution
4167    * as well as a fair duration */
4168   rate = qtmux->trak_timescale ?
4169       qtmux->trak_timescale : atom_framerate_to_timescale (framerate_num,
4170       framerate_den);
4171   GST_DEBUG_OBJECT (qtmux, "Rate of video track selected: %" G_GUINT32_FORMAT,
4172       rate);
4173
4174   /* set common properties */
4175   entry.width = width;
4176   entry.height = height;
4177   entry.par_n = par_num;
4178   entry.par_d = par_den;
4179   /* should be OK according to qt and iso spec, override if really needed */
4180   entry.color_table_id = -1;
4181   entry.frame_count = 1;
4182   entry.depth = 24;
4183
4184   /* sync entries by default */
4185   sync = TRUE;
4186
4187   /* now map onto a fourcc, and some extra properties */
4188   if (strcmp (mimetype, "video/x-raw") == 0) {
4189     const gchar *format;
4190     GstVideoFormat fmt;
4191     const GstVideoFormatInfo *vinfo;
4192
4193     format = gst_structure_get_string (structure, "format");
4194     fmt = gst_video_format_from_string (format);
4195     vinfo = gst_video_format_get_info (fmt);
4196
4197     switch (fmt) {
4198       case GST_VIDEO_FORMAT_UYVY:
4199         if (depth == -1)
4200           depth = 24;
4201         entry.fourcc = FOURCC_2vuy;
4202         entry.depth = depth;
4203         sync = FALSE;
4204         break;
4205       case GST_VIDEO_FORMAT_v210:
4206         if (depth == -1)
4207           depth = 24;
4208         entry.fourcc = FOURCC_v210;
4209         entry.depth = depth;
4210         sync = FALSE;
4211         break;
4212       default:
4213         if (GST_VIDEO_FORMAT_INFO_FLAGS (vinfo) & GST_VIDEO_FORMAT_FLAG_RGB) {
4214           entry.fourcc = FOURCC_raw_;
4215           entry.depth = GST_VIDEO_FORMAT_INFO_PSTRIDE (vinfo, 0) * 8;
4216           sync = FALSE;
4217         }
4218         break;
4219     }
4220   } else if (strcmp (mimetype, "video/x-h263") == 0) {
4221     ext_atom = NULL;
4222     if (format == GST_QT_MUX_FORMAT_QT)
4223       entry.fourcc = FOURCC_h263;
4224     else
4225       entry.fourcc = FOURCC_s263;
4226     ext_atom = build_h263_extension ();
4227     if (ext_atom != NULL)
4228       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
4229   } else if (strcmp (mimetype, "video/x-divx") == 0 ||
4230       strcmp (mimetype, "video/mpeg") == 0) {
4231     gint version = 0;
4232
4233     if (strcmp (mimetype, "video/x-divx") == 0) {
4234       gst_structure_get_int (structure, "divxversion", &version);
4235       version = version == 5 ? 1 : 0;
4236     } else {
4237       gst_structure_get_int (structure, "mpegversion", &version);
4238       version = version == 4 ? 1 : 0;
4239     }
4240     if (version) {
4241       entry.fourcc = FOURCC_mp4v;
4242       ext_atom =
4243           build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P2,
4244           ESDS_STREAM_TYPE_VISUAL, codec_data, qtpad->avg_bitrate,
4245           qtpad->max_bitrate);
4246       if (ext_atom != NULL)
4247         ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
4248       if (!codec_data)
4249         GST_WARNING_OBJECT (qtmux, "no codec_data for MPEG4 video; "
4250             "output might not play in Apple QuickTime (try global-headers?)");
4251     }
4252   } else if (strcmp (mimetype, "video/x-h264") == 0) {
4253     if (!codec_data) {
4254       GST_WARNING_OBJECT (qtmux, "no codec_data in h264 caps");
4255       goto refuse_caps;
4256     }
4257
4258     entry.fourcc = FOURCC_avc1;
4259
4260     ext_atom = build_btrt_extension (0, qtpad->avg_bitrate, qtpad->max_bitrate);
4261     if (ext_atom != NULL)
4262       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
4263     ext_atom = build_codec_data_extension (FOURCC_avcC, codec_data);
4264     if (ext_atom != NULL)
4265       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
4266   } else if (strcmp (mimetype, "video/x-h265") == 0) {
4267     const gchar *format;
4268
4269     if (!codec_data) {
4270       GST_WARNING_OBJECT (qtmux, "no codec_data in h265 caps");
4271       goto refuse_caps;
4272     }
4273
4274     format = gst_structure_get_string (structure, "stream-format");
4275     if (strcmp (format, "hvc1") == 0)
4276       entry.fourcc = FOURCC_hvc1;
4277     else if (strcmp (format, "hev1") == 0)
4278       entry.fourcc = FOURCC_hev1;
4279
4280     ext_atom = build_btrt_extension (0, qtpad->avg_bitrate, qtpad->max_bitrate);
4281     if (ext_atom != NULL)
4282       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
4283
4284     ext_atom = build_codec_data_extension (FOURCC_hvcC, codec_data);
4285     if (ext_atom != NULL)
4286       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
4287
4288   } else if (strcmp (mimetype, "video/x-svq") == 0) {
4289     gint version = 0;
4290     const GstBuffer *seqh = NULL;
4291     const GValue *seqh_value;
4292     gdouble gamma = 0;
4293
4294     gst_structure_get_int (structure, "svqversion", &version);
4295     if (version == 3) {
4296       entry.fourcc = FOURCC_SVQ3;
4297       entry.version = 3;
4298       entry.depth = 32;
4299
4300       seqh_value = gst_structure_get_value (structure, "seqh");
4301       if (seqh_value) {
4302         seqh = gst_value_get_buffer (seqh_value);
4303         ext_atom = build_SMI_atom (seqh);
4304         if (ext_atom)
4305           ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
4306       }
4307
4308       /* we need to add the gamma anyway because quicktime might crash
4309        * when it doesn't find it */
4310       if (!gst_structure_get_double (structure, "applied-gamma", &gamma)) {
4311         /* it seems that using 0 here makes it ignored */
4312         gamma = 0.0;
4313       }
4314       ext_atom = build_gama_atom (gamma);
4315       if (ext_atom)
4316         ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
4317     } else {
4318       GST_WARNING_OBJECT (qtmux, "SVQ version %d not supported. Please file "
4319           "a bug at http://bugzilla.gnome.org", version);
4320     }
4321   } else if (strcmp (mimetype, "video/x-dv") == 0) {
4322     gint version = 0;
4323     gboolean pal = TRUE;
4324
4325     sync = FALSE;
4326     if (framerate_num != 25 || framerate_den != 1)
4327       pal = FALSE;
4328     gst_structure_get_int (structure, "dvversion", &version);
4329     /* fall back to typical one */
4330     if (!version)
4331       version = 25;
4332     switch (version) {
4333       case 25:
4334         if (pal)
4335           entry.fourcc = FOURCC_dvcp;
4336         else
4337           entry.fourcc = FOURCC_dvc_;
4338         break;
4339       case 50:
4340         if (pal)
4341           entry.fourcc = FOURCC_dv5p;
4342         else
4343           entry.fourcc = FOURCC_dv5n;
4344         break;
4345       default:
4346         GST_WARNING_OBJECT (qtmux, "unrecognized dv version");
4347         break;
4348     }
4349   } else if (strcmp (mimetype, "image/jpeg") == 0) {
4350     entry.fourcc = FOURCC_jpeg;
4351     sync = FALSE;
4352   } else if (strcmp (mimetype, "image/x-j2c") == 0 ||
4353       strcmp (mimetype, "image/x-jpc") == 0) {
4354     const gchar *colorspace;
4355     const GValue *cmap_array;
4356     const GValue *cdef_array;
4357     gint ncomp = 0;
4358
4359     if (strcmp (mimetype, "image/x-jpc") == 0) {
4360       qtpad->prepare_buf_func = gst_qt_mux_prepare_jpc_buffer;
4361     }
4362
4363     gst_structure_get_int (structure, "num-components", &ncomp);
4364     cmap_array = gst_structure_get_value (structure, "component-map");
4365     cdef_array = gst_structure_get_value (structure, "channel-definitions");
4366
4367     ext_atom = NULL;
4368     entry.fourcc = FOURCC_mjp2;
4369     sync = FALSE;
4370
4371     colorspace = gst_structure_get_string (structure, "colorspace");
4372     if (colorspace &&
4373         (ext_atom =
4374             build_jp2h_extension (width, height, colorspace, ncomp, cmap_array,
4375                 cdef_array)) != NULL) {
4376       ext_atom_list = g_list_append (ext_atom_list, ext_atom);
4377
4378       ext_atom = build_jp2x_extension (codec_data);
4379       if (ext_atom)
4380         ext_atom_list = g_list_append (ext_atom_list, ext_atom);
4381     } else {
4382       GST_DEBUG_OBJECT (qtmux, "missing or invalid fourcc in jp2 caps");
4383       goto refuse_caps;
4384     }
4385   } else if (strcmp (mimetype, "video/x-vp8") == 0) {
4386     entry.fourcc = FOURCC_VP80;
4387     sync = FALSE;
4388   } else if (strcmp (mimetype, "video/x-dirac") == 0) {
4389     entry.fourcc = FOURCC_drac;
4390   } else if (strcmp (mimetype, "video/x-qt-part") == 0) {
4391     guint32 fourcc = 0;
4392
4393     gst_structure_get_uint (structure, "format", &fourcc);
4394     entry.fourcc = fourcc;
4395   } else if (strcmp (mimetype, "video/x-mp4-part") == 0) {
4396     guint32 fourcc = 0;
4397
4398     gst_structure_get_uint (structure, "format", &fourcc);
4399     entry.fourcc = fourcc;
4400   } else if (strcmp (mimetype, "video/x-prores") == 0) {
4401     const gchar *variant;
4402
4403     variant = gst_structure_get_string (structure, "variant");
4404     if (!variant || !g_strcmp0 (variant, "standard"))
4405       entry.fourcc = FOURCC_apcn;
4406     else if (!g_strcmp0 (variant, "lt"))
4407       entry.fourcc = FOURCC_apcs;
4408     else if (!g_strcmp0 (variant, "hq"))
4409       entry.fourcc = FOURCC_apch;
4410     else if (!g_strcmp0 (variant, "proxy"))
4411       entry.fourcc = FOURCC_apco;
4412     else if (!g_strcmp0 (variant, "4444"))
4413       entry.fourcc = FOURCC_ap4h;
4414     else if (!g_strcmp0 (variant, "4444xq"))
4415       entry.fourcc = FOURCC_ap4x;
4416
4417     sync = FALSE;
4418
4419     if (!qtmux->interleave_time_set)
4420       qtmux->interleave_time = 500 * GST_MSECOND;
4421     if (!qtmux->interleave_bytes_set)
4422       qtmux->interleave_bytes = width > 720 ? 4 * 1024 * 1024 : 2 * 1024 * 1024;
4423   } else if (strcmp (mimetype, "video/x-cineform") == 0) {
4424     entry.fourcc = FOURCC_cfhd;
4425     sync = FALSE;
4426   }
4427
4428   if (!entry.fourcc)
4429     goto refuse_caps;
4430
4431   if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT ||
4432       qtmux_klass->format == GST_QT_MUX_FORMAT_MP4) {
4433     const gchar *s;
4434     GstVideoColorimetry colorimetry;
4435
4436     s = gst_structure_get_string (structure, "colorimetry");
4437     if (s && gst_video_colorimetry_from_string (&colorimetry, s)) {
4438       ext_atom =
4439           build_colr_extension (&colorimetry,
4440           qtmux_klass->format == GST_QT_MUX_FORMAT_MP4);
4441       if (ext_atom)
4442         ext_atom_list = g_list_append (ext_atom_list, ext_atom);
4443     }
4444   }
4445
4446   if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT
4447       || strcmp (mimetype, "image/x-j2c") == 0
4448       || strcmp (mimetype, "image/x-jpc") == 0) {
4449     const gchar *s;
4450     GstVideoInterlaceMode interlace_mode;
4451     GstVideoFieldOrder field_order;
4452     gint fields = -1;
4453
4454     if (strcmp (mimetype, "image/x-j2c") == 0 ||
4455         strcmp (mimetype, "image/x-jpc") == 0) {
4456
4457       fields = 1;
4458       gst_structure_get_int (structure, "fields", &fields);
4459     }
4460
4461     s = gst_structure_get_string (structure, "interlace-mode");
4462     if (s)
4463       interlace_mode = gst_video_interlace_mode_from_string (s);
4464     else
4465       interlace_mode =
4466           (fields <=
4467           1) ? GST_VIDEO_INTERLACE_MODE_PROGRESSIVE :
4468           GST_VIDEO_INTERLACE_MODE_MIXED;
4469
4470     field_order = GST_VIDEO_FIELD_ORDER_UNKNOWN;
4471     if (interlace_mode == GST_VIDEO_INTERLACE_MODE_INTERLEAVED) {
4472       s = gst_structure_get_string (structure, "field-order");
4473       if (s)
4474         field_order = gst_video_field_order_from_string (s);
4475     }
4476
4477     ext_atom = build_fiel_extension (interlace_mode, field_order);
4478     if (ext_atom)
4479       ext_atom_list = g_list_append (ext_atom_list, ext_atom);
4480   }
4481
4482
4483   if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT &&
4484       width > 640 && width <= 1052 && height >= 480 && height <= 576) {
4485     /* The 'clap' extension is also defined for MP4 but inventing values in
4486      * general seems a bit tricky for this one. We only write it for
4487      * SD resolution in MOV, where it is a requirement.
4488      * The same goes for the 'tapt' extension, just that it is not defined for
4489      * MP4 and only for MOV
4490      */
4491     gint dar_num, dar_den;
4492     gint clef_width, clef_height, prof_width;
4493     gint clap_width_n, clap_width_d, clap_height;
4494     gint cdiv;
4495     double approx_dar;
4496
4497     /* First, guess display aspect ratio based on pixel aspect ratio,
4498      * width and height. We assume that display aspect ratio is either
4499      * 4:3 or 16:9
4500      */
4501     approx_dar = (gdouble) (width * par_num) / (height * par_den);
4502     if (approx_dar > 11.0 / 9 && approx_dar < 14.0 / 9) {
4503       dar_num = 4;
4504       dar_den = 3;
4505     } else if (approx_dar > 15.0 / 9 && approx_dar < 18.0 / 9) {
4506       dar_num = 16;
4507       dar_den = 9;
4508     } else {
4509       dar_num = width * par_num;
4510       dar_den = height * par_den;
4511       cdiv = gst_util_greatest_common_divisor (dar_num, dar_den);
4512       dar_num /= cdiv;
4513       dar_den /= cdiv;
4514     }
4515
4516     /* Then, calculate clean-aperture values (clap and clef)
4517      * using the guessed DAR.
4518      */
4519     clef_height = clap_height = (height == 486 ? 480 : height);
4520     clef_width = gst_util_uint64_scale (clef_height,
4521         dar_num * G_GUINT64_CONSTANT (65536), dar_den);
4522     prof_width = gst_util_uint64_scale (width,
4523         par_num * G_GUINT64_CONSTANT (65536), par_den);
4524     clap_width_n = clap_height * dar_num * par_den;
4525     clap_width_d = dar_den * par_num;
4526     cdiv = gst_util_greatest_common_divisor (clap_width_n, clap_width_d);
4527     clap_width_n /= cdiv;
4528     clap_width_d /= cdiv;
4529
4530     ext_atom = build_tapt_extension (clef_width, clef_height << 16, prof_width,
4531         height << 16, width << 16, height << 16);
4532     qtpad->trak->tapt = ext_atom;
4533
4534     ext_atom = build_clap_extension (clap_width_n, clap_width_d,
4535         clap_height, 1, 0, 1, 0, 1);
4536     if (ext_atom)
4537       ext_atom_list = g_list_append (ext_atom_list, ext_atom);
4538   }
4539
4540   /* ok, set the pad info accordingly */
4541   qtpad->fourcc = entry.fourcc;
4542   qtpad->sync = sync;
4543   qtpad->trak_ste =
4544       (SampleTableEntry *) atom_trak_set_video_type (qtpad->trak,
4545       qtmux->context, &entry, rate, ext_atom_list);
4546   if (strcmp (mimetype, "video/x-prores") == 0) {
4547     SampleTableEntryMP4V *mp4v = (SampleTableEntryMP4V *) qtpad->trak_ste;
4548     const gchar *compressor = NULL;
4549     mp4v->spatial_quality = 0x3FF;
4550     mp4v->temporal_quality = 0;
4551     mp4v->vendor = FOURCC_appl;
4552     mp4v->horizontal_resolution = 72 << 16;
4553     mp4v->vertical_resolution = 72 << 16;
4554     mp4v->depth = (entry.fourcc == FOURCC_ap4h
4555         || entry.fourcc == FOURCC_ap4x) ? 32 : 24;
4556
4557     /* Set compressor name, required by some software */
4558     switch (entry.fourcc) {
4559       case FOURCC_apcn:
4560         compressor = "Apple ProRes 422";
4561         break;
4562       case FOURCC_apcs:
4563         compressor = "Apple ProRes 422 LT";
4564         break;
4565       case FOURCC_apch:
4566         compressor = "Apple ProRes 422 HQ";
4567         break;
4568       case FOURCC_apco:
4569         compressor = "Apple ProRes 422 Proxy";
4570         break;
4571       case FOURCC_ap4h:
4572         compressor = "Apple ProRes 4444";
4573         break;
4574       case FOURCC_ap4x:
4575         compressor = "Apple ProRes 4444 XQ";
4576         break;
4577     }
4578     if (compressor) {
4579       strcpy ((gchar *) mp4v->compressor + 1, compressor);
4580       mp4v->compressor[0] = strlen (compressor);
4581     }
4582   }
4583
4584   gst_object_unref (qtmux);
4585   return TRUE;
4586
4587   /* ERRORS */
4588 refuse_caps:
4589   {
4590     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
4591         GST_PAD_NAME (pad), caps);
4592     gst_object_unref (qtmux);
4593     return FALSE;
4594   }
4595 refuse_renegotiation:
4596   {
4597     GST_WARNING_OBJECT (qtmux,
4598         "pad %s refused renegotiation to %" GST_PTR_FORMAT, GST_PAD_NAME (pad),
4599         caps);
4600     gst_object_unref (qtmux);
4601     return FALSE;
4602   }
4603 }
4604
4605 static gboolean
4606 gst_qt_mux_subtitle_sink_set_caps (GstQTPad * qtpad, GstCaps * caps)
4607 {
4608   GstPad *pad = qtpad->collect.pad;
4609   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
4610   GstStructure *structure;
4611   SubtitleSampleEntry entry = { 0, };
4612
4613   /* does not go well to renegotiate stream mid-way, unless
4614    * the old caps are a subset of the new one (this means upstream
4615    * added more info to the caps, as both should be 'fixed' caps) */
4616   if (qtpad->fourcc) {
4617     GstCaps *current_caps;
4618
4619     current_caps = gst_pad_get_current_caps (pad);
4620     g_assert (caps != NULL);
4621
4622     if (!gst_qtmux_caps_is_subset_full (qtmux, current_caps, caps)) {
4623       gst_caps_unref (current_caps);
4624       goto refuse_renegotiation;
4625     }
4626     GST_DEBUG_OBJECT (qtmux,
4627         "pad %s accepted renegotiation to %" GST_PTR_FORMAT " from %"
4628         GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, current_caps);
4629     gst_caps_unref (current_caps);
4630   }
4631
4632   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
4633       GST_DEBUG_PAD_NAME (pad), caps);
4634
4635   /* subtitles default */
4636   subtitle_sample_entry_init (&entry);
4637   qtpad->is_out_of_order = FALSE;
4638   qtpad->sync = FALSE;
4639   qtpad->sparse = TRUE;
4640   qtpad->prepare_buf_func = NULL;
4641
4642   structure = gst_caps_get_structure (caps, 0);
4643
4644   if (gst_structure_has_name (structure, "text/x-raw")) {
4645     const gchar *format = gst_structure_get_string (structure, "format");
4646     if (format && strcmp (format, "utf8") == 0) {
4647       entry.fourcc = FOURCC_tx3g;
4648       qtpad->prepare_buf_func = gst_qt_mux_prepare_tx3g_buffer;
4649       qtpad->create_empty_buffer = gst_qt_mux_create_empty_tx3g_buffer;
4650     }
4651   }
4652
4653   if (!entry.fourcc)
4654     goto refuse_caps;
4655
4656   qtpad->fourcc = entry.fourcc;
4657   qtpad->trak_ste =
4658       (SampleTableEntry *) atom_trak_set_subtitle_type (qtpad->trak,
4659       qtmux->context, &entry);
4660
4661   gst_object_unref (qtmux);
4662   return TRUE;
4663
4664   /* ERRORS */
4665 refuse_caps:
4666   {
4667     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
4668         GST_PAD_NAME (pad), caps);
4669     gst_object_unref (qtmux);
4670     return FALSE;
4671   }
4672 refuse_renegotiation:
4673   {
4674     GST_WARNING_OBJECT (qtmux,
4675         "pad %s refused renegotiation to %" GST_PTR_FORMAT, GST_PAD_NAME (pad),
4676         caps);
4677     gst_object_unref (qtmux);
4678     return FALSE;
4679   }
4680 }
4681
4682 static gboolean
4683 gst_qt_mux_sink_event (GstCollectPads * pads, GstCollectData * data,
4684     GstEvent * event, gpointer user_data)
4685 {
4686   GstQTMux *qtmux;
4687   guint32 avg_bitrate = 0, max_bitrate = 0;
4688   GstPad *pad = data->pad;
4689   gboolean ret = TRUE;
4690
4691   qtmux = GST_QT_MUX_CAST (user_data);
4692   switch (GST_EVENT_TYPE (event)) {
4693     case GST_EVENT_CAPS:
4694     {
4695       GstCaps *caps;
4696       GstQTPad *collect_pad;
4697
4698       gst_event_parse_caps (event, &caps);
4699
4700       /* find stream data */
4701       collect_pad = (GstQTPad *) gst_pad_get_element_private (pad);
4702       g_assert (collect_pad);
4703       g_assert (collect_pad->set_caps);
4704
4705       ret = collect_pad->set_caps (collect_pad, caps);
4706       gst_event_unref (event);
4707       event = NULL;
4708       break;
4709     }
4710     case GST_EVENT_TAG:{
4711       GstTagList *list;
4712       GstTagSetter *setter = GST_TAG_SETTER (qtmux);
4713       GstTagMergeMode mode;
4714       gchar *code;
4715       GstQTPad *collect_pad;
4716
4717       GST_OBJECT_LOCK (qtmux);
4718       mode = gst_tag_setter_get_tag_merge_mode (setter);
4719       collect_pad = (GstQTPad *) gst_pad_get_element_private (pad);
4720
4721       gst_event_parse_tag (event, &list);
4722       GST_DEBUG_OBJECT (qtmux, "received tag event on pad %s:%s : %"
4723           GST_PTR_FORMAT, GST_DEBUG_PAD_NAME (pad), list);
4724
4725       if (gst_tag_list_get_scope (list) == GST_TAG_SCOPE_GLOBAL) {
4726         gst_tag_setter_merge_tags (setter, list, mode);
4727         qtmux->tags_changed = TRUE;
4728       } else {
4729         if (!collect_pad->tags)
4730           collect_pad->tags = gst_tag_list_new_empty ();
4731         gst_tag_list_insert (collect_pad->tags, list, mode);
4732         collect_pad->tags_changed = TRUE;
4733       }
4734       GST_OBJECT_UNLOCK (qtmux);
4735
4736       if (gst_tag_list_get_uint (list, GST_TAG_BITRATE, &avg_bitrate) |
4737           gst_tag_list_get_uint (list, GST_TAG_MAXIMUM_BITRATE, &max_bitrate)) {
4738         GstQTPad *qtpad = gst_pad_get_element_private (pad);
4739         g_assert (qtpad);
4740
4741         if (avg_bitrate > 0 && avg_bitrate < G_MAXUINT32)
4742           qtpad->avg_bitrate = avg_bitrate;
4743         if (max_bitrate > 0 && max_bitrate < G_MAXUINT32)
4744           qtpad->max_bitrate = max_bitrate;
4745       }
4746
4747       if (gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &code)) {
4748         const char *iso_code = gst_tag_get_language_code_iso_639_2T (code);
4749         if (iso_code) {
4750           GstQTPad *qtpad = gst_pad_get_element_private (pad);
4751           g_assert (qtpad);
4752           if (qtpad->trak) {
4753             /* https://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap4/qtff4.html */
4754             qtpad->trak->mdia.mdhd.language_code =
4755                 (iso_code[0] - 0x60) * 0x400 + (iso_code[1] - 0x60) * 0x20 +
4756                 (iso_code[2] - 0x60);
4757           }
4758         }
4759         g_free (code);
4760       }
4761
4762       gst_event_unref (event);
4763       event = NULL;
4764       ret = TRUE;
4765       break;
4766     }
4767     default:
4768       break;
4769   }
4770
4771   if (event != NULL)
4772     return gst_collect_pads_event_default (pads, data, event, FALSE);
4773
4774   return ret;
4775 }
4776
4777 static void
4778 gst_qt_mux_release_pad (GstElement * element, GstPad * pad)
4779 {
4780   GstQTMux *mux = GST_QT_MUX_CAST (element);
4781   GSList *walk;
4782
4783   GST_DEBUG_OBJECT (element, "Releasing %s:%s", GST_DEBUG_PAD_NAME (pad));
4784
4785   for (walk = mux->sinkpads; walk; walk = g_slist_next (walk)) {
4786     GstQTPad *qtpad = (GstQTPad *) walk->data;
4787     GST_DEBUG ("Checking %s:%s", GST_DEBUG_PAD_NAME (qtpad->collect.pad));
4788     if (qtpad->collect.pad == pad) {
4789       /* this is it, remove */
4790       mux->sinkpads = g_slist_delete_link (mux->sinkpads, walk);
4791       gst_element_remove_pad (element, pad);
4792       break;
4793     }
4794   }
4795
4796   if (mux->current_pad && mux->current_pad->collect.pad == pad) {
4797     mux->current_pad = NULL;
4798     mux->current_chunk_size = 0;
4799     mux->current_chunk_duration = 0;
4800   }
4801
4802   gst_collect_pads_remove_pad (mux->collect, pad);
4803
4804   if (mux->sinkpads == NULL) {
4805     /* No more outstanding request pads, reset our counters */
4806     mux->video_pads = 0;
4807     mux->audio_pads = 0;
4808     mux->subtitle_pads = 0;
4809   }
4810 }
4811
4812 static GstPad *
4813 gst_qt_mux_request_new_pad (GstElement * element,
4814     GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps)
4815 {
4816   GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
4817   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
4818   GstQTPad *collect_pad;
4819   GstPad *newpad;
4820   GstQTPadSetCapsFunc setcaps_func;
4821   gchar *name;
4822   gint pad_id;
4823   gboolean lock = TRUE;
4824
4825   if (templ->direction != GST_PAD_SINK)
4826     goto wrong_direction;
4827
4828   if (qtmux->state > GST_QT_MUX_STATE_STARTED)
4829     goto too_late;
4830
4831   if (templ == gst_element_class_get_pad_template (klass, "audio_%u")) {
4832     setcaps_func = gst_qt_mux_audio_sink_set_caps;
4833     if (req_name != NULL && sscanf (req_name, "audio_%u", &pad_id) == 1) {
4834       name = g_strdup (req_name);
4835     } else {
4836       name = g_strdup_printf ("audio_%u", qtmux->audio_pads++);
4837     }
4838   } else if (templ == gst_element_class_get_pad_template (klass, "video_%u")) {
4839     setcaps_func = gst_qt_mux_video_sink_set_caps;
4840     if (req_name != NULL && sscanf (req_name, "video_%u", &pad_id) == 1) {
4841       name = g_strdup (req_name);
4842     } else {
4843       name = g_strdup_printf ("video_%u", qtmux->video_pads++);
4844     }
4845   } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%u")) {
4846     setcaps_func = gst_qt_mux_subtitle_sink_set_caps;
4847     if (req_name != NULL && sscanf (req_name, "subtitle_%u", &pad_id) == 1) {
4848       name = g_strdup (req_name);
4849     } else {
4850       name = g_strdup_printf ("subtitle_%u", qtmux->subtitle_pads++);
4851     }
4852     lock = FALSE;
4853   } else
4854     goto wrong_template;
4855
4856   GST_DEBUG_OBJECT (qtmux, "Requested pad: %s", name);
4857
4858   /* create pad and add to collections */
4859   newpad = gst_pad_new_from_template (templ, name);
4860   g_free (name);
4861   collect_pad = (GstQTPad *)
4862       gst_collect_pads_add_pad (qtmux->collect, newpad, sizeof (GstQTPad),
4863       (GstCollectDataDestroyNotify) (gst_qt_mux_pad_reset), lock);
4864   /* set up pad */
4865   gst_qt_mux_pad_reset (collect_pad);
4866   collect_pad->trak = atom_trak_new (qtmux->context);
4867   atom_moov_add_trak (qtmux->moov, collect_pad->trak);
4868
4869   qtmux->sinkpads = g_slist_append (qtmux->sinkpads, collect_pad);
4870
4871   /* set up pad functions */
4872   collect_pad->set_caps = setcaps_func;
4873
4874   gst_pad_set_active (newpad, TRUE);
4875   gst_element_add_pad (element, newpad);
4876
4877   return newpad;
4878
4879   /* ERRORS */
4880 wrong_direction:
4881   {
4882     GST_WARNING_OBJECT (qtmux, "Request pad that is not a SINK pad.");
4883     return NULL;
4884   }
4885 too_late:
4886   {
4887     GST_WARNING_OBJECT (qtmux, "Not providing request pad after stream start.");
4888     return NULL;
4889   }
4890 wrong_template:
4891   {
4892     GST_WARNING_OBJECT (qtmux, "This is not our template!");
4893     return NULL;
4894   }
4895 }
4896
4897 static void
4898 gst_qt_mux_get_property (GObject * object,
4899     guint prop_id, GValue * value, GParamSpec * pspec)
4900 {
4901   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
4902
4903   GST_OBJECT_LOCK (qtmux);
4904   switch (prop_id) {
4905     case PROP_MOVIE_TIMESCALE:
4906       g_value_set_uint (value, qtmux->timescale);
4907       break;
4908     case PROP_TRAK_TIMESCALE:
4909       g_value_set_uint (value, qtmux->trak_timescale);
4910       break;
4911     case PROP_DO_CTTS:
4912       g_value_set_boolean (value, qtmux->guess_pts);
4913       break;
4914 #ifndef GST_REMOVE_DEPRECATED
4915     case PROP_DTS_METHOD:
4916       g_value_set_enum (value, qtmux->dts_method);
4917       break;
4918 #endif
4919     case PROP_FAST_START:
4920       g_value_set_boolean (value, qtmux->fast_start);
4921       break;
4922     case PROP_FAST_START_TEMP_FILE:
4923       g_value_set_string (value, qtmux->fast_start_file_path);
4924       break;
4925     case PROP_MOOV_RECOV_FILE:
4926       g_value_set_string (value, qtmux->moov_recov_file_path);
4927       break;
4928     case PROP_FRAGMENT_DURATION:
4929       g_value_set_uint (value, qtmux->fragment_duration);
4930       break;
4931     case PROP_STREAMABLE:
4932       g_value_set_boolean (value, qtmux->streamable);
4933       break;
4934     case PROP_RESERVED_MAX_DURATION:
4935       g_value_set_uint64 (value, qtmux->reserved_max_duration);
4936       break;
4937     case PROP_RESERVED_DURATION_REMAINING:
4938       if (qtmux->reserved_duration_remaining == GST_CLOCK_TIME_NONE)
4939         g_value_set_uint64 (value, qtmux->reserved_max_duration);
4940       else {
4941         GstClockTime remaining = qtmux->reserved_duration_remaining;
4942
4943         /* Report the remaining space as the calculated remaining, minus
4944          * however much we've muxed since the last update */
4945         if (remaining > qtmux->muxed_since_last_update)
4946           remaining -= qtmux->muxed_since_last_update;
4947         else
4948           remaining = 0;
4949         GST_LOG_OBJECT (qtmux, "reserved duration remaining - reporting %"
4950             G_GUINT64_FORMAT "(%" G_GUINT64_FORMAT " - %" G_GUINT64_FORMAT,
4951             remaining, qtmux->reserved_duration_remaining,
4952             qtmux->muxed_since_last_update);
4953         g_value_set_uint64 (value, remaining);
4954       }
4955       break;
4956     case PROP_RESERVED_MOOV_UPDATE_PERIOD:
4957       g_value_set_uint64 (value, qtmux->reserved_moov_update_period);
4958       break;
4959     case PROP_RESERVED_BYTES_PER_SEC:
4960       g_value_set_uint (value, qtmux->reserved_bytes_per_sec_per_trak);
4961       break;
4962     case PROP_INTERLEAVE_BYTES:
4963       g_value_set_uint64 (value, qtmux->interleave_bytes);
4964       break;
4965     case PROP_INTERLEAVE_TIME:
4966       g_value_set_uint64 (value, qtmux->interleave_time);
4967       break;
4968     case PROP_MAX_RAW_AUDIO_DRIFT:
4969       g_value_set_uint64 (value, qtmux->max_raw_audio_drift);
4970       break;
4971     default:
4972       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
4973       break;
4974   }
4975   GST_OBJECT_UNLOCK (qtmux);
4976 }
4977
4978 static void
4979 gst_qt_mux_generate_fast_start_file_path (GstQTMux * qtmux)
4980 {
4981   gchar *tmp;
4982
4983   g_free (qtmux->fast_start_file_path);
4984   qtmux->fast_start_file_path = NULL;
4985
4986   tmp = g_strdup_printf ("%s%d", "qtmux", g_random_int ());
4987   qtmux->fast_start_file_path = g_build_filename (g_get_tmp_dir (), tmp, NULL);
4988   g_free (tmp);
4989 }
4990
4991 static void
4992 gst_qt_mux_set_property (GObject * object,
4993     guint prop_id, const GValue * value, GParamSpec * pspec)
4994 {
4995   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
4996
4997   GST_OBJECT_LOCK (qtmux);
4998   switch (prop_id) {
4999     case PROP_MOVIE_TIMESCALE:
5000       qtmux->timescale = g_value_get_uint (value);
5001       break;
5002     case PROP_TRAK_TIMESCALE:
5003       qtmux->trak_timescale = g_value_get_uint (value);
5004       break;
5005     case PROP_DO_CTTS:
5006       qtmux->guess_pts = g_value_get_boolean (value);
5007       break;
5008 #ifndef GST_REMOVE_DEPRECATED
5009     case PROP_DTS_METHOD:
5010       qtmux->dts_method = g_value_get_enum (value);
5011       break;
5012 #endif
5013     case PROP_FAST_START:
5014       qtmux->fast_start = g_value_get_boolean (value);
5015       break;
5016     case PROP_FAST_START_TEMP_FILE:
5017       g_free (qtmux->fast_start_file_path);
5018       qtmux->fast_start_file_path = g_value_dup_string (value);
5019       /* NULL means to generate a random one */
5020       if (!qtmux->fast_start_file_path) {
5021         gst_qt_mux_generate_fast_start_file_path (qtmux);
5022       }
5023       break;
5024     case PROP_MOOV_RECOV_FILE:
5025       g_free (qtmux->moov_recov_file_path);
5026       qtmux->moov_recov_file_path = g_value_dup_string (value);
5027       break;
5028     case PROP_FRAGMENT_DURATION:
5029       qtmux->fragment_duration = g_value_get_uint (value);
5030       break;
5031     case PROP_STREAMABLE:{
5032       GstQTMuxClass *qtmux_klass =
5033           (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
5034       if (qtmux_klass->format == GST_QT_MUX_FORMAT_ISML) {
5035         qtmux->streamable = g_value_get_boolean (value);
5036       }
5037       break;
5038     }
5039     case PROP_RESERVED_MAX_DURATION:
5040       qtmux->reserved_max_duration = g_value_get_uint64 (value);
5041       break;
5042     case PROP_RESERVED_MOOV_UPDATE_PERIOD:
5043       qtmux->reserved_moov_update_period = g_value_get_uint64 (value);
5044       break;
5045     case PROP_RESERVED_BYTES_PER_SEC:
5046       qtmux->reserved_bytes_per_sec_per_trak = g_value_get_uint (value);
5047       break;
5048     case PROP_INTERLEAVE_BYTES:
5049       qtmux->interleave_bytes = g_value_get_uint64 (value);
5050       qtmux->interleave_bytes_set = TRUE;
5051       break;
5052     case PROP_INTERLEAVE_TIME:
5053       qtmux->interleave_time = g_value_get_uint64 (value);
5054       qtmux->interleave_time_set = TRUE;
5055       break;
5056     case PROP_MAX_RAW_AUDIO_DRIFT:
5057       qtmux->max_raw_audio_drift = g_value_get_uint64 (value);
5058       break;
5059     default:
5060       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
5061       break;
5062   }
5063   GST_OBJECT_UNLOCK (qtmux);
5064 }
5065
5066 static GstStateChangeReturn
5067 gst_qt_mux_change_state (GstElement * element, GstStateChange transition)
5068 {
5069   GstStateChangeReturn ret;
5070   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
5071
5072   switch (transition) {
5073     case GST_STATE_CHANGE_NULL_TO_READY:
5074       break;
5075     case GST_STATE_CHANGE_READY_TO_PAUSED:
5076       gst_collect_pads_start (qtmux->collect);
5077       qtmux->state = GST_QT_MUX_STATE_STARTED;
5078       break;
5079     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
5080       break;
5081     case GST_STATE_CHANGE_PAUSED_TO_READY:
5082       gst_collect_pads_stop (qtmux->collect);
5083       break;
5084     default:
5085       break;
5086   }
5087
5088   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
5089
5090   switch (transition) {
5091     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
5092       break;
5093     case GST_STATE_CHANGE_PAUSED_TO_READY:
5094       gst_qt_mux_reset (qtmux, TRUE);
5095       break;
5096     case GST_STATE_CHANGE_READY_TO_NULL:
5097       break;
5098     default:
5099       break;
5100   }
5101
5102   return ret;
5103 }
5104
5105 gboolean
5106 gst_qt_mux_register (GstPlugin * plugin)
5107 {
5108   GTypeInfo typeinfo = {
5109     sizeof (GstQTMuxClass),
5110     (GBaseInitFunc) gst_qt_mux_base_init,
5111     NULL,
5112     (GClassInitFunc) gst_qt_mux_class_init,
5113     NULL,
5114     NULL,
5115     sizeof (GstQTMux),
5116     0,
5117     (GInstanceInitFunc) gst_qt_mux_init,
5118   };
5119   static const GInterfaceInfo tag_setter_info = {
5120     NULL, NULL, NULL
5121   };
5122   static const GInterfaceInfo tag_xmp_writer_info = {
5123     NULL, NULL, NULL
5124   };
5125   static const GInterfaceInfo preset_info = {
5126     NULL, NULL, NULL
5127   };
5128   GType type;
5129   GstQTMuxFormat format;
5130   GstQTMuxClassParams *params;
5131   guint i = 0;
5132
5133   GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
5134
5135   GST_LOG ("Registering muxers");
5136
5137   while (TRUE) {
5138     GstQTMuxFormatProp *prop;
5139     GstCaps *subtitle_caps;
5140
5141     prop = &gst_qt_mux_format_list[i];
5142     format = prop->format;
5143     if (format == GST_QT_MUX_FORMAT_NONE)
5144       break;
5145
5146     /* create a cache for these properties */
5147     params = g_new0 (GstQTMuxClassParams, 1);
5148     params->prop = prop;
5149     params->src_caps = gst_static_caps_get (&prop->src_caps);
5150     params->video_sink_caps = gst_static_caps_get (&prop->video_sink_caps);
5151     params->audio_sink_caps = gst_static_caps_get (&prop->audio_sink_caps);
5152     subtitle_caps = gst_static_caps_get (&prop->subtitle_sink_caps);
5153     if (!gst_caps_is_equal (subtitle_caps, GST_CAPS_NONE)) {
5154       params->subtitle_sink_caps = subtitle_caps;
5155     } else {
5156       gst_caps_unref (subtitle_caps);
5157     }
5158
5159     /* create the type now */
5160     type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo,
5161         0);
5162     g_type_set_qdata (type, GST_QT_MUX_PARAMS_QDATA, (gpointer) params);
5163     g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
5164     g_type_add_interface_static (type, GST_TYPE_TAG_XMP_WRITER,
5165         &tag_xmp_writer_info);
5166     g_type_add_interface_static (type, GST_TYPE_PRESET, &preset_info);
5167
5168     if (!gst_element_register (plugin, prop->name, prop->rank, type))
5169       return FALSE;
5170
5171     i++;
5172   }
5173
5174   GST_LOG ("Finished registering muxers");
5175
5176   /* FIXME: ideally classification tag should be added and
5177      registered in gstreamer core gsttaglist
5178    */
5179
5180   GST_LOG ("Registering tags");
5181
5182   gst_tag_register (GST_TAG_3GP_CLASSIFICATION, GST_TAG_FLAG_META,
5183       G_TYPE_STRING, GST_TAG_3GP_CLASSIFICATION, "content classification",
5184       gst_tag_merge_use_first);
5185
5186   GST_LOG ("Finished registering tags");
5187
5188   return TRUE;
5189 }