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