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