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