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