e12ab332b2afa3f68f26e5a2a85022a06110a8f9
[platform/upstream/gst-plugins-good.git] / gst / quicktime / gstqtmux.c
1 /* Quicktime muxer plugin for GStreamer
2  * Copyright (C) 2008 Thiago Sousa 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 };
113
114 /* some spare for header size as well */
115 #define MDAT_LARGE_FILE_LIMIT           ((guint64) 1024 * 1024 * 1024 * 2)
116 #define MAX_TOLERATED_LATENESS          (GST_SECOND / 10)
117
118 #define DEFAULT_LARGE_FILE              FALSE
119 #define DEFAULT_MOVIE_TIMESCALE         1000
120 #define DEFAULT_DO_CTTS                 FALSE
121 #define DEFAULT_FAST_START              FALSE
122 #define DEFAULT_FAST_START_TEMP_FILE    NULL
123
124 static void gst_qt_mux_finalize (GObject * object);
125
126 static GstStateChangeReturn gst_qt_mux_change_state (GstElement * element,
127     GstStateChange transition);
128
129 /* property functions */
130 static void gst_qt_mux_set_property (GObject * object,
131     guint prop_id, const GValue * value, GParamSpec * pspec);
132 static void gst_qt_mux_get_property (GObject * object,
133     guint prop_id, GValue * value, GParamSpec * pspec);
134
135 /* pad functions */
136 static GstPad *gst_qt_mux_request_new_pad (GstElement * element,
137     GstPadTemplate * templ, const gchar * name);
138 static void gst_qt_mux_release_pad (GstElement * element, GstPad * pad);
139
140 /* event */
141 static gboolean gst_qt_mux_sink_event (GstPad * pad, GstEvent * event);
142
143 static GstFlowReturn gst_qt_mux_collected (GstCollectPads * pads,
144     gpointer user_data);
145 static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
146     GstBuffer * buf);
147
148 static GstElementClass *parent_class = NULL;
149
150 static void
151 gst_qt_mux_base_init (gpointer g_class)
152 {
153   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
154   GstQTMuxClass *klass = (GstQTMuxClass *) g_class;
155   GstQTMuxClassParams *params;
156   GstElementDetails details;
157   GstPadTemplate *videosinktempl, *audiosinktempl, *srctempl;
158
159   params =
160       (GstQTMuxClassParams *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (g_class),
161       GST_QT_MUX_PARAMS_QDATA);
162   g_assert (params != NULL);
163
164   /* construct the element details struct */
165   details.longname = g_strdup_printf ("%s Muxer", params->prop->long_name);
166   details.klass = g_strdup ("Codec/Muxer");
167   details.description =
168       g_strdup_printf ("Multiplex audio and video into a %s file",
169       params->prop->long_name);
170   details.author = "Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>";
171   gst_element_class_set_details (element_class, &details);
172   g_free (details.longname);
173   g_free (details.klass);
174   g_free (details.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));
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));
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, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
227   g_object_class_install_property (gobject_class, PROP_FAST_START,
228       g_param_spec_boolean ("faststart", "Format file to faststart",
229           "If the file should be formated for faststart (headers first). ",
230           DEFAULT_FAST_START, G_PARAM_READWRITE));
231   g_object_class_install_property (gobject_class, PROP_FAST_START_TEMP_FILE,
232       g_param_spec_string ("faststart-file", "File to use for storing buffers",
233           "File that will be used temporarily to store data from the stream "
234           "when creating a faststart file. If null a filepath will be "
235           "created automatically", DEFAULT_FAST_START_TEMP_FILE,
236           G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
237
238   gstelement_class->request_new_pad =
239       GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
240   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qt_mux_change_state);
241   gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_release_pad);
242 }
243
244 static void
245 gst_qt_mux_pad_reset (GstQTPad * qtpad)
246 {
247   qtpad->fourcc = 0;
248   qtpad->is_out_of_order = FALSE;
249   qtpad->have_dts = FALSE;
250   qtpad->sample_size = 0;
251   qtpad->sync = FALSE;
252   qtpad->last_dts = 0;
253   qtpad->first_ts = GST_CLOCK_TIME_NONE;
254
255   if (qtpad->last_buf)
256     gst_buffer_replace (&qtpad->last_buf, NULL);
257
258   /* reference owned elsewhere */
259   qtpad->trak = NULL;
260 }
261
262 /*
263  * Takes GstQTMux back to its initial state
264  */
265 static void
266 gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
267 {
268   GSList *walk;
269
270   qtmux->state = GST_QT_MUX_STATE_NONE;
271   qtmux->header_size = 0;
272   qtmux->mdat_size = 0;
273   qtmux->mdat_pos = 0;
274   qtmux->longest_chunk = GST_CLOCK_TIME_NONE;
275
276   if (qtmux->ftyp) {
277     atom_ftyp_free (qtmux->ftyp);
278     qtmux->ftyp = NULL;
279   }
280   if (qtmux->moov) {
281     atom_moov_free (qtmux->moov);
282     qtmux->moov = NULL;
283   }
284   if (qtmux->fast_start_file) {
285     fclose (qtmux->fast_start_file);
286     qtmux->fast_start_file = NULL;
287   }
288   gst_tag_setter_reset_tags (GST_TAG_SETTER (qtmux));
289
290   /* reset pad data */
291   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
292     GstQTPad *qtpad = (GstQTPad *) walk->data;
293     gst_qt_mux_pad_reset (qtpad);
294
295     /* hm, moov_free above yanked the traks away from us,
296      * so do not free, but do clear */
297     qtpad->trak = NULL;
298   }
299
300   if (alloc) {
301     qtmux->moov = atom_moov_new (qtmux->context);
302     /* ensure all is as nice and fresh as request_new_pad would provide it */
303     for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
304       GstQTPad *qtpad = (GstQTPad *) walk->data;
305
306       qtpad->trak = atom_trak_new (qtmux->context);
307       atom_moov_add_trak (qtmux->moov, qtpad->trak);
308     }
309   }
310 }
311
312 static void
313 gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
314 {
315   GstElementClass *klass = GST_ELEMENT_CLASS (qtmux_klass);
316   GstPadTemplate *templ;
317   GstCaps *caps;
318
319   templ = gst_element_class_get_pad_template (klass, "src");
320   qtmux->srcpad = gst_pad_new_from_template (templ, "src");
321   caps = gst_caps_copy (gst_pad_get_pad_template_caps (qtmux->srcpad));
322   gst_pad_set_caps (qtmux->srcpad, caps);
323   gst_caps_unref (caps);
324   gst_pad_use_fixed_caps (qtmux->srcpad);
325   gst_element_add_pad (GST_ELEMENT (qtmux), qtmux->srcpad);
326
327   qtmux->collect = gst_collect_pads_new ();
328   gst_collect_pads_set_function (qtmux->collect,
329       (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_qt_mux_collected), qtmux);
330
331   /* properties set to default upon construction */
332
333   /* always need this */
334   qtmux->context =
335       atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format));
336
337   /* internals to initial state */
338   gst_qt_mux_reset (qtmux, TRUE);
339 }
340
341
342 static void
343 gst_qt_mux_finalize (GObject * object)
344 {
345   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
346
347   gst_qt_mux_reset (qtmux, FALSE);
348
349   if (qtmux->fast_start_file_path)
350     g_free (qtmux->fast_start_file_path);
351
352   atoms_context_free (qtmux->context);
353   gst_object_unref (qtmux->collect);
354
355   G_OBJECT_CLASS (parent_class)->finalize (object);
356 }
357
358 static void
359 gst_qt_mux_add_mp4_tag (GstQTMux * qtmux, const GstTagList * list,
360     const char *tag, const char *tag2, guint32 fourcc)
361 {
362   switch (gst_tag_get_type (tag)) {
363       /* strings */
364     case G_TYPE_STRING:
365     {
366       gchar *str = NULL;
367
368       if (!gst_tag_list_get_string (list, tag, &str) || !str)
369         break;
370       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
371           GST_FOURCC_ARGS (fourcc), str);
372       atom_moov_add_str_tag (qtmux->moov, fourcc, str);
373       g_free (str);
374       break;
375     }
376       /* double */
377     case G_TYPE_DOUBLE:
378     {
379       gdouble value;
380
381       if (!gst_tag_list_get_double (list, tag, &value))
382         break;
383       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
384           GST_FOURCC_ARGS (fourcc), (gint) value);
385       atom_moov_add_uint_tag (qtmux->moov, fourcc, 21, (gint) value);
386       break;
387     }
388       /* paired unsigned integers */
389     case G_TYPE_UINT:
390     {
391       guint value;
392       guint count;
393
394       if (!gst_tag_list_get_uint (list, tag, &value) ||
395           !gst_tag_list_get_uint (list, tag2, &count))
396         break;
397       GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u",
398           GST_FOURCC_ARGS (fourcc), value, count);
399       atom_moov_add_uint_tag (qtmux->moov, fourcc, 0,
400           value << 16 | (count & 0xFFFF));
401       break;
402     }
403     default:
404       g_assert_not_reached ();
405       break;
406   }
407 }
408
409 static void
410 gst_qt_mux_add_mp4_date (GstQTMux * qtmux, const GstTagList * list,
411     const char *tag, const char *tag2, guint32 fourcc)
412 {
413   GDate *date = NULL;
414   GDateYear year;
415   GDateMonth month;
416   GDateDay day;
417   gchar *str;
418
419   g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_DATE);
420
421   if (!gst_tag_list_get_date (list, tag, &date) || !date)
422     return;
423
424   year = g_date_get_year (date);
425   month = g_date_get_month (date);
426   day = g_date_get_day (date);
427
428   if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH &&
429       day == G_DATE_BAD_DAY) {
430     GST_WARNING_OBJECT (qtmux, "invalid date in tag");
431     return;
432   }
433
434   str = g_strdup_printf ("%u-%u-%u", year, month, day);
435   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
436       GST_FOURCC_ARGS (fourcc), str);
437   atom_moov_add_str_tag (qtmux->moov, fourcc, str);
438   g_free (str);
439 }
440
441 static void
442 gst_qt_mux_add_mp4_cover (GstQTMux * qtmux, const GstTagList * list,
443     const char *tag, const char *tag2, guint32 fourcc)
444 {
445   GValue value = { 0, };
446   GstBuffer *buf;
447   GstCaps *caps;
448   GstStructure *structure;
449   gint flags = 0;
450
451   g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_BUFFER);
452
453   if (!gst_tag_list_copy_value (&value, list, tag))
454     return;
455
456   buf = gst_value_get_buffer (&value);
457   if (!buf)
458     goto done;
459
460   caps = gst_buffer_get_caps (buf);
461   if (!caps) {
462     GST_WARNING_OBJECT (qtmux, "preview image without caps");
463     goto done;
464   }
465
466   GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);
467
468   structure = gst_caps_get_structure (caps, 0);
469   if (gst_structure_has_name (structure, "image/jpeg"))
470     flags = 13;
471   else if (gst_structure_has_name (structure, "image/png"))
472     flags = 14;
473   gst_caps_unref (caps);
474
475   if (!flags) {
476     GST_WARNING_OBJECT (qtmux, "preview image format not supported");
477     goto done;
478   }
479
480   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT
481       " -> image size %d", GST_FOURCC_ARGS (fourcc), GST_BUFFER_SIZE (buf));
482   atom_moov_add_tag (qtmux->moov, fourcc, flags, GST_BUFFER_DATA (buf),
483       GST_BUFFER_SIZE (buf));
484 done:
485   g_value_unset (&value);
486 }
487
488 static void
489 gst_qt_mux_add_3gp_str (GstQTMux * qtmux, const GstTagList * list,
490     const char *tag, const char *tag2, guint32 fourcc)
491 {
492   gchar *str = NULL;
493   guint number;
494
495   g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_STRING);
496   g_return_if_fail (!tag2 || gst_tag_get_type (tag2) == G_TYPE_UINT);
497
498   if (!gst_tag_list_get_string (list, tag, &str) || !str)
499     return;
500
501   if (tag2)
502     if (!gst_tag_list_get_uint (list, tag2, &number))
503       tag2 = NULL;
504
505   if (!tag2) {
506     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
507         GST_FOURCC_ARGS (fourcc), str);
508     atom_moov_add_3gp_str_tag (qtmux->moov, fourcc, str);
509   } else {
510     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s/%d",
511         GST_FOURCC_ARGS (fourcc), str, number);
512     atom_moov_add_3gp_str_int_tag (qtmux->moov, fourcc, str, number);
513   }
514
515   g_free (str);
516 }
517
518 static void
519 gst_qt_mux_add_3gp_date (GstQTMux * qtmux, const GstTagList * list,
520     const char *tag, const char *tag2, guint32 fourcc)
521 {
522   GDate *date = NULL;
523   GDateYear year;
524
525   g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_DATE);
526
527   if (!gst_tag_list_get_date (list, tag, &date) || !date)
528     return;
529
530   year = g_date_get_year (date);
531
532   if (year == G_DATE_BAD_YEAR) {
533     GST_WARNING_OBJECT (qtmux, "invalid date in tag");
534     return;
535   }
536
537   GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %d",
538       GST_FOURCC_ARGS (fourcc), year);
539   atom_moov_add_3gp_uint_tag (qtmux->moov, fourcc, year);
540 }
541
542 static void
543 gst_qt_mux_add_3gp_location (GstQTMux * qtmux, const GstTagList * list,
544     const char *tag, const char *tag2, guint32 fourcc)
545 {
546   gdouble latitude = -360, longitude = -360, altitude = 0;
547   gchar *location = NULL;
548   guint8 *data, *ddata;
549   gint size = 0, len = 0;
550   gboolean ret = FALSE;
551
552   g_return_if_fail (strcmp (tag, GST_TAG_GEO_LOCATION_NAME) == 0);
553
554   ret = gst_tag_list_get_string (list, tag, &location);
555   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LONGITUDE,
556       &longitude);
557   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LATITUDE,
558       &latitude);
559   ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_ELEVATION,
560       &altitude);
561
562   if (!ret)
563     return;
564
565   if (location)
566     len = strlen (location);
567   size += len + 1 + 2;
568
569   /* role + (long, lat, alt) + body + notes */
570   size += 1 + 3 * 4 + 1 + 1;
571
572   data = ddata = g_malloc (size);
573
574   /* language tag */
575   GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
576   /* location */
577   if (location)
578     memcpy (data + 2, location, len);
579   GST_WRITE_UINT8 (data + 2 + len, 0);
580   data += len + 1 + 2;
581   /* role */
582   GST_WRITE_UINT8 (data, 0);
583   /* long, lat, alt */
584   GST_WRITE_UINT32_BE (data + 1, (guint32) (longitude * 65536.0));
585   GST_WRITE_UINT32_BE (data + 5, (guint32) (latitude * 65536.0));
586   GST_WRITE_UINT32_BE (data + 9, (guint32) (altitude * 65536.0));
587   /* neither astronomical body nor notes */
588   GST_WRITE_UINT16_BE (data + 13, 0);
589
590   GST_DEBUG_OBJECT (qtmux, "Adding tag 'loci'");
591   atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
592   g_free (ddata);
593 }
594
595 static void
596 gst_qt_mux_add_3gp_keywords (GstQTMux * qtmux, const GstTagList * list,
597     const char *tag, const char *tag2, guint32 fourcc)
598 {
599   gchar *keywords = NULL;
600   guint8 *data, *ddata;
601   gint size = 0, i;
602   gchar **kwds;
603
604   g_return_if_fail (strcmp (tag, GST_TAG_KEYWORDS) == 0);
605
606   if (!gst_tag_list_get_string (list, tag, &keywords) || !keywords)
607     return;
608
609   kwds = g_strsplit (keywords, ",", 0);
610
611   size = 0;
612   for (i = 0; kwds[i]; i++) {
613     /* size byte + null-terminator */
614     size += strlen (kwds[i]) + 1 + 1;
615   }
616
617   /* language tag + count + keywords */
618   size += 2 + 1;
619
620   data = ddata = g_malloc (size);
621
622   /* language tag */
623   GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
624   /* count */
625   GST_WRITE_UINT8 (data + 2, i);
626   data += 3;
627   /* keywords */
628   for (i = 0; kwds[i]; ++i) {
629     gint len = strlen (kwds[i]);
630
631     GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
632         GST_FOURCC_ARGS (fourcc), kwds[i]);
633     /* size */
634     GST_WRITE_UINT8 (data, len + 1);
635     memcpy (data + 1, kwds[i], len + 1);
636     data += len + 2;
637   }
638
639   g_strfreev (kwds);
640
641   atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
642   g_free (ddata);
643 }
644
645
646 typedef void (*GstQTMuxAddTagFunc) (GstQTMux * mux, const GstTagList * list,
647     const char *tag, const char *tag2, guint32 fourcc);
648
649 /*
650  * Struct to record mappings from gstreamer tags to fourcc codes
651  */
652 typedef struct _GstTagToFourcc
653 {
654   guint32 fourcc;
655   const gchar *gsttag;
656   const gchar *gsttag2;
657   const GstQTMuxAddTagFunc func;
658 } GstTagToFourcc;
659
660 /* tag list tags to fourcc matching */
661 static const GstTagToFourcc tag_matches_mp4[] = {
662   {FOURCC__alb, GST_TAG_ALBUM, NULL, gst_qt_mux_add_mp4_tag},
663   {FOURCC__ART, GST_TAG_ARTIST, NULL, gst_qt_mux_add_mp4_tag},
664   {FOURCC__cmt, GST_TAG_COMMENT, NULL, gst_qt_mux_add_mp4_tag},
665   {FOURCC__wrt, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_mp4_tag},
666   {FOURCC__gen, GST_TAG_GENRE, NULL, gst_qt_mux_add_mp4_tag},
667   {FOURCC__nam, GST_TAG_TITLE, NULL, gst_qt_mux_add_mp4_tag},
668   {FOURCC__des, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_mp4_tag},
669   {FOURCC__too, GST_TAG_ENCODER, NULL, gst_qt_mux_add_mp4_tag},
670   {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_mp4_tag},
671   {FOURCC_keyw, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_mp4_tag},
672   {FOURCC__day, GST_TAG_DATE, NULL, gst_qt_mux_add_mp4_date},
673   {FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE, NULL, gst_qt_mux_add_mp4_tag},
674   {FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT,
675       gst_qt_mux_add_mp4_tag},
676   {FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT,
677       gst_qt_mux_add_mp4_tag},
678   {FOURCC_covr, GST_TAG_PREVIEW_IMAGE, NULL, gst_qt_mux_add_mp4_cover},
679   {0, NULL,}
680 };
681
682 static const GstTagToFourcc tag_matches_3gp[] = {
683   {FOURCC_titl, GST_TAG_TITLE, NULL, gst_qt_mux_add_3gp_str},
684   {FOURCC_dscp, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_3gp_str},
685   {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_3gp_str},
686   {FOURCC_perf, GST_TAG_ARTIST, NULL, gst_qt_mux_add_3gp_str},
687   {FOURCC_auth, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_3gp_str},
688   {FOURCC_gnre, GST_TAG_GENRE, NULL, gst_qt_mux_add_3gp_str},
689   {FOURCC_kywd, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_3gp_keywords},
690   {FOURCC_yrrc, GST_TAG_DATE, NULL, gst_qt_mux_add_3gp_date},
691   {FOURCC_albm, GST_TAG_ALBUM, GST_TAG_TRACK_NUMBER, gst_qt_mux_add_3gp_str},
692   {FOURCC_loci, GST_TAG_GEO_LOCATION_NAME, NULL, gst_qt_mux_add_3gp_location},
693   {0, NULL,}
694 };
695
696 /* qtdemux produces these for atoms it cannot parse */
697 #define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag"
698
699 static void
700 gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list)
701 {
702   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
703   guint32 fourcc;
704   gint i;
705   const gchar *tag, *tag2;
706   const GstTagToFourcc *tag_matches;
707
708   switch (qtmux_klass->format) {
709     case GST_QT_MUX_FORMAT_3GP:
710       tag_matches = tag_matches_3gp;
711       break;
712     case GST_QT_MUX_FORMAT_MJ2:
713       tag_matches = NULL;
714       break;
715     default:
716       /* sort of iTunes style for mp4 and QT (?) */
717       tag_matches = tag_matches_mp4;
718       break;
719   }
720
721   if (!tag_matches)
722     return;
723
724   for (i = 0; tag_matches[i].fourcc; i++) {
725     fourcc = tag_matches[i].fourcc;
726     tag = tag_matches[i].gsttag;
727     tag2 = tag_matches[i].gsttag2;
728
729     g_assert (tag_matches[i].func);
730     tag_matches[i].func (qtmux, list, tag, tag2, fourcc);
731   }
732
733   /* add unparsed blobs if present */
734   if (gst_tag_exists (GST_QT_DEMUX_PRIVATE_TAG)) {
735     guint num_tags;
736
737     num_tags = gst_tag_list_get_tag_size (list, GST_QT_DEMUX_PRIVATE_TAG);
738     for (i = 0; i < num_tags; ++i) {
739       const GValue *val;
740       GstBuffer *buf;
741       GstCaps *caps = NULL;
742
743       val = gst_tag_list_get_value_index (list, GST_QT_DEMUX_PRIVATE_TAG, i);
744       buf = (GstBuffer *) gst_value_get_mini_object (val);
745
746       if (buf && (caps = gst_buffer_get_caps (buf))) {
747         GstStructure *s;
748         const gchar *style = NULL;
749
750         GST_DEBUG_OBJECT (qtmux, "Found private tag %d/%d; size %d, caps %"
751             GST_PTR_FORMAT, i, num_tags, GST_BUFFER_SIZE (buf), caps);
752         s = gst_caps_get_structure (caps, 0);
753         if (s && (style = gst_structure_get_string (s, "style"))) {
754           /* try to prevent some style tag ending up into another variant
755            * (todo: make into a list if more cases) */
756           if ((strcmp (style, "itunes") == 0 &&
757                   qtmux_klass->format == GST_QT_MUX_FORMAT_MP4) ||
758               (strcmp (style, "iso") == 0 &&
759                   qtmux_klass->format == GST_QT_MUX_FORMAT_3GP)) {
760             GST_DEBUG_OBJECT (qtmux, "Adding private tag");
761             atom_moov_add_blob_tag (qtmux->moov, GST_BUFFER_DATA (buf),
762                 GST_BUFFER_SIZE (buf));
763           }
764         }
765         gst_caps_unref (caps);
766       }
767     }
768   }
769
770   return;
771 }
772
773 /*
774  * Gets the tagsetter iface taglist and puts the known tags
775  * into the output stream
776  */
777 static void
778 gst_qt_mux_setup_metadata (GstQTMux * qtmux)
779 {
780   const GstTagList *tags;
781
782   tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (qtmux));
783
784   GST_LOG_OBJECT (qtmux, "tags: %" GST_PTR_FORMAT, tags);
785
786   if (tags && !gst_tag_list_is_empty (tags)) {
787     GST_DEBUG_OBJECT (qtmux, "Formatting tags");
788     gst_qt_mux_add_metadata_tags (qtmux, tags);
789   } else {
790     GST_DEBUG_OBJECT (qtmux, "No tags received");
791   }
792 }
793
794 static GstFlowReturn
795 gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset,
796     gboolean mind_fast)
797 {
798   GstFlowReturn res;
799   guint8 *data;
800   guint size;
801
802   g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
803
804   data = GST_BUFFER_DATA (buf);
805   size = GST_BUFFER_SIZE (buf);
806
807   GST_LOG_OBJECT (qtmux, "sending buffer size %d", size);
808
809   if (mind_fast && qtmux->fast_start_file) {
810     gint ret;
811
812     GST_LOG_OBJECT (qtmux, "to temporary file");
813     ret = fwrite (data, sizeof (guint8), size, qtmux->fast_start_file);
814     gst_buffer_unref (buf);
815     if (ret != size)
816       goto write_error;
817     else
818       res = GST_FLOW_OK;
819   } else {
820     GST_LOG_OBJECT (qtmux, "downstream");
821
822     buf = gst_buffer_make_metadata_writable (buf);
823     gst_buffer_set_caps (buf, GST_PAD_CAPS (qtmux->srcpad));
824     res = gst_pad_push (qtmux->srcpad, buf);
825   }
826
827   if (G_LIKELY (offset))
828     *offset += size;
829
830   return res;
831
832   /* ERRORS */
833 write_error:
834   {
835     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
836         ("Failed to write to temporary file"), GST_ERROR_SYSTEM);
837     return GST_FLOW_ERROR;
838   }
839 }
840
841 static GstFlowReturn
842 gst_qt_mux_send_buffered_data (GstQTMux * qtmux, guint64 * offset)
843 {
844   GstFlowReturn ret = GST_FLOW_OK;
845   GstBuffer *buf = NULL;
846
847   if (fflush (qtmux->fast_start_file))
848     goto flush_failed;
849
850 #ifdef HAVE_FSEEKO
851   if (fseeko (qtmux->fast_start_file, (off_t) 0, SEEK_SET) != 0)
852     goto seek_failed;
853 #elif defined (G_OS_UNIX) || defined (G_OS_WIN32)
854   if (lseek (fileno (qtmux->fast_start_file), (off_t) 0,
855           SEEK_SET) == (off_t) - 1)
856     goto seek_failed;
857 #else
858   if (fseek (qtmux->fast_start_file, (long) 0, SEEK_SET) != 0)
859     goto seek_failed;
860 #endif
861
862   /* hm, this could all take a really really long time,
863    * but there may not be another way to get moov atom first
864    * (somehow optimize copy?) */
865   GST_DEBUG_OBJECT (qtmux, "Sending buffered data");
866   while (ret == GST_FLOW_OK) {
867     gint r;
868     const int bufsize = 4096;
869
870     buf = gst_buffer_new_and_alloc (bufsize);
871     r = fread (GST_BUFFER_DATA (buf), sizeof (guint8), bufsize,
872         qtmux->fast_start_file);
873     if (r == 0)
874       break;
875     GST_BUFFER_SIZE (buf) = r;
876     GST_LOG_OBJECT (qtmux, "Pushing buffered buffer of size %d", r);
877     ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
878     buf = NULL;
879   }
880   if (buf)
881     gst_buffer_unref (buf);
882
883 exit:
884   /* best cleaning up effort, eat possible error */
885   fclose (qtmux->fast_start_file);
886   qtmux->fast_start_file = NULL;
887
888   /* FIXME maybe delete temporary file, or let the system handle that ? */
889
890   return ret;
891
892   /* ERRORS */
893 flush_failed:
894   {
895     GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
896         ("Failed to flush temporary file"), GST_ERROR_SYSTEM);
897     ret = GST_FLOW_ERROR;
898     goto exit;
899   }
900 seek_failed:
901   {
902     GST_ELEMENT_ERROR (qtmux, RESOURCE, SEEK,
903         ("Failed to seek temporary file"), GST_ERROR_SYSTEM);
904     ret = GST_FLOW_ERROR;
905     goto exit;
906   }
907 }
908
909 /*
910  * Sends the initial mdat atom fields (size fields and fourcc type),
911  * the subsequent buffers are considered part of it's data.
912  * As we can't predict the amount of data that we are going to place in mdat
913  * we need to record the position of the size field in the stream so we can
914  * seek back to it later and update when the streams have finished.
915  */
916 static GstFlowReturn
917 gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size,
918     gboolean extended)
919 {
920   Atom *node_header;
921   GstBuffer *buf;
922   guint8 *data = NULL;
923   guint64 offset = 0;
924
925   GST_DEBUG_OBJECT (qtmux, "Sending mdat's atom header, "
926       "size %" G_GUINT64_FORMAT, size);
927
928   node_header = g_malloc0 (sizeof (Atom));
929   node_header->type = FOURCC_mdat;
930   if (extended) {
931     /* use extended size */
932     node_header->size = 1;
933     node_header->extended_size = 0;
934     if (size)
935       node_header->extended_size = size + 16;
936   } else {
937     node_header->size = size + 8;
938   }
939
940   size = offset = 0;
941   if (atom_copy_data (node_header, &data, &size, &offset) == 0)
942     goto serialize_error;
943
944   buf = gst_buffer_new ();
945   GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
946   GST_BUFFER_SIZE (buf) = offset;
947
948   g_free (node_header);
949
950   GST_LOG_OBJECT (qtmux, "Pushing mdat start");
951   return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
952
953   /* ERRORS */
954 serialize_error:
955   {
956     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
957         ("Failed to serialize ftyp"));
958     return GST_FLOW_ERROR;
959   }
960 }
961
962 /*
963  * We get the position of the mdat size field, seek back to it
964  * and overwrite with the real value
965  */
966 static GstFlowReturn
967 gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos,
968     guint64 mdat_size, guint64 * offset)
969 {
970   GstEvent *event;
971   GstBuffer *buf;
972   gboolean large_file;
973
974   large_file = (mdat_size > MDAT_LARGE_FILE_LIMIT);
975
976   if (large_file)
977     mdat_pos += 8;
978
979   /* seek and rewrite the header */
980   event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES,
981       mdat_pos, GST_CLOCK_TIME_NONE, 0);
982   gst_pad_push_event (qtmux->srcpad, event);
983
984   if (large_file) {
985     buf = gst_buffer_new_and_alloc (sizeof (guint64));
986     GST_WRITE_UINT64_BE (GST_BUFFER_DATA (buf), mdat_size + 16);
987   } else {
988     guint8 *data;
989
990     buf = gst_buffer_new_and_alloc (16);
991     data = GST_BUFFER_DATA (buf);
992     GST_WRITE_UINT32_BE (data, 8);
993     GST_WRITE_UINT32_LE (data + 4, FOURCC_free);
994     GST_WRITE_UINT32_BE (data + 8, mdat_size + 8);
995     GST_WRITE_UINT32_LE (data + 12, FOURCC_mdat);
996   }
997
998   return gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
999 }
1000
1001 static GstFlowReturn
1002 gst_qt_mux_send_ftyp (GstQTMux * qtmux, guint64 * off)
1003 {
1004   GstBuffer *buf;
1005   guint64 size = 0, offset = 0;
1006   guint8 *data = NULL;
1007
1008   GST_DEBUG_OBJECT (qtmux, "Sending ftyp atom");
1009
1010   if (!atom_ftyp_copy_data (qtmux->ftyp, &data, &size, &offset))
1011     goto serialize_error;
1012
1013   buf = gst_buffer_new ();
1014   GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
1015   GST_BUFFER_SIZE (buf) = offset;
1016
1017   GST_LOG_OBJECT (qtmux, "Pushing ftyp");
1018   return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
1019
1020   /* ERRORS */
1021 serialize_error:
1022   {
1023     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1024         ("Failed to serialize ftyp"));
1025     return GST_FLOW_ERROR;
1026   }
1027 }
1028
1029 static GstFlowReturn
1030 gst_qt_mux_prepare_and_send_ftyp (GstQTMux * qtmux)
1031 {
1032   GstFlowReturn ret = GST_FLOW_OK;
1033   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1034   guint32 major, version;
1035   GList *comp;
1036   GstBuffer *prefix;
1037
1038   GST_DEBUG_OBJECT (qtmux, "Preparing to send ftyp atom");
1039
1040   /* init and send context and ftyp based on current property state */
1041   if (qtmux->ftyp)
1042     atom_ftyp_free (qtmux->ftyp);
1043   gst_qt_mux_map_format_to_header (qtmux_klass->format, &prefix, &major,
1044       &version, &comp, qtmux->moov, qtmux->longest_chunk,
1045       qtmux->fast_start_file != NULL);
1046   qtmux->ftyp = atom_ftyp_new (qtmux->context, major, version, comp);
1047   if (comp)
1048     g_list_free (comp);
1049   if (prefix) {
1050     ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE);
1051     if (ret != GST_FLOW_OK)
1052       return ret;
1053   }
1054   return gst_qt_mux_send_ftyp (qtmux, &qtmux->header_size);
1055 }
1056
1057 static GstFlowReturn
1058 gst_qt_mux_start_file (GstQTMux * qtmux)
1059 {
1060   GstFlowReturn ret = GST_FLOW_OK;
1061
1062   GST_DEBUG_OBJECT (qtmux, "starting file");
1063
1064   /* let downstream know we think in BYTES and expect to do seeking later on */
1065   gst_pad_push_event (qtmux->srcpad,
1066       gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0));
1067
1068   /* 
1069    * send mdat header if already needed, and mark position for later update.
1070    * We don't send ftyp now if we are on fast start mode, because we can
1071    * better fine tune using the information we gather to create the whole moov
1072    * atom.
1073    */
1074   if (qtmux->fast_start) {
1075     GST_OBJECT_LOCK (qtmux);
1076     qtmux->fast_start_file = g_fopen (qtmux->fast_start_file_path, "wb+");
1077     if (!qtmux->fast_start_file)
1078       goto open_failed;
1079     GST_OBJECT_UNLOCK (qtmux);
1080
1081     /* send a dummy buffer for preroll */
1082     ret = gst_qt_mux_send_buffer (qtmux, gst_buffer_new (), NULL, FALSE);
1083     if (ret != GST_FLOW_OK)
1084       goto exit;
1085
1086   } else {
1087     ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
1088     if (ret != GST_FLOW_OK) {
1089       goto exit;
1090     }
1091
1092     /* extended to ensure some spare space */
1093     qtmux->mdat_pos = qtmux->header_size;
1094     ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0, TRUE);
1095   }
1096
1097 exit:
1098   return ret;
1099
1100   /* ERRORS */
1101 open_failed:
1102   {
1103     GST_ELEMENT_ERROR (qtmux, RESOURCE, OPEN_READ_WRITE,
1104         (("Could not open temporary file \"%s\""), qtmux->fast_start_file_path),
1105         GST_ERROR_SYSTEM);
1106     GST_OBJECT_UNLOCK (qtmux);
1107     return GST_FLOW_ERROR;
1108   }
1109 }
1110
1111 static GstFlowReturn
1112 gst_qt_mux_stop_file (GstQTMux * qtmux)
1113 {
1114   gboolean ret = GST_FLOW_OK;
1115   GstBuffer *buffer = NULL;
1116   guint64 offset = 0, size = 0;
1117   guint8 *data;
1118   GSList *walk;
1119   gboolean large_file;
1120   guint32 timescale;
1121   GstClockTime first_ts = GST_CLOCK_TIME_NONE;
1122
1123   GST_DEBUG_OBJECT (qtmux, "Updating remaining values and sending last data");
1124
1125   /* pushing last buffers for each pad */
1126   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
1127     GstCollectData *cdata = (GstCollectData *) walk->data;
1128     GstQTPad *qtpad = (GstQTPad *) cdata;
1129
1130     /* send last buffer */
1131     GST_DEBUG_OBJECT (qtmux, "Sending the last buffer for pad %s",
1132         GST_PAD_NAME (qtpad->collect.pad));
1133     ret = gst_qt_mux_add_buffer (qtmux, qtpad, NULL);
1134     if (ret != GST_FLOW_OK)
1135       GST_WARNING_OBJECT (qtmux, "Failed to send last buffer for %s, "
1136           "flow return: %s", GST_PAD_NAME (qtpad->collect.pad),
1137           gst_flow_get_name (ret));
1138   }
1139
1140   GST_OBJECT_LOCK (qtmux);
1141   timescale = qtmux->timescale;
1142   large_file = qtmux->large_file;
1143   GST_OBJECT_UNLOCK (qtmux);
1144
1145   /* inform lower layers of our property wishes, and determine duration.
1146    * Let moov take care of this using its list of traks;
1147    * so that released pads are also included */
1148   GST_DEBUG_OBJECT (qtmux, "Large file support: %d", large_file);
1149   GST_DEBUG_OBJECT (qtmux, "Updating timescale to %" G_GUINT32_FORMAT,
1150       timescale);
1151   atom_moov_update_timescale (qtmux->moov, timescale);
1152   atom_moov_set_64bits (qtmux->moov, large_file);
1153   atom_moov_update_duration (qtmux->moov);
1154
1155   /* check for late streams */
1156   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
1157     GstCollectData *cdata = (GstCollectData *) walk->data;
1158     GstQTPad *qtpad = (GstQTPad *) cdata;
1159
1160     if (!GST_CLOCK_TIME_IS_VALID (first_ts) ||
1161         (GST_CLOCK_TIME_IS_VALID (qtpad->first_ts) &&
1162             qtpad->first_ts < first_ts)) {
1163       first_ts = qtpad->first_ts;
1164     }
1165   }
1166   GST_DEBUG_OBJECT (qtmux, "Media first ts selected: %" GST_TIME_FORMAT,
1167       GST_TIME_ARGS (first_ts));
1168   /* add EDTSs for late streams */
1169   for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
1170     GstCollectData *cdata = (GstCollectData *) walk->data;
1171     GstQTPad *qtpad = (GstQTPad *) cdata;
1172     guint32 lateness;
1173     guint32 duration;
1174
1175     if (GST_CLOCK_TIME_IS_VALID (qtpad->first_ts) &&
1176         qtpad->first_ts > first_ts + MAX_TOLERATED_LATENESS) {
1177       GST_DEBUG_OBJECT (qtmux, "Pad %s is a late stream by %" GST_TIME_FORMAT,
1178           GST_PAD_NAME (qtpad->collect.pad),
1179           GST_TIME_ARGS (qtpad->first_ts - first_ts));
1180       lateness = gst_util_uint64_scale_round (qtpad->first_ts - first_ts,
1181           timescale, GST_SECOND);
1182       duration = qtpad->trak->tkhd.duration;
1183       atom_trak_add_elst_entry (qtpad->trak, lateness, (guint32) - 1,
1184           (guint32) (1 * 65536.0));
1185       atom_trak_add_elst_entry (qtpad->trak, duration, 0,
1186           (guint32) (1 * 65536.0));
1187
1188       /* need to add the empty time to the trak duration */
1189       qtpad->trak->tkhd.duration += lateness;
1190     }
1191   }
1192
1193   /* tags into file metadata */
1194   gst_qt_mux_setup_metadata (qtmux);
1195
1196   large_file = (qtmux->mdat_size > MDAT_LARGE_FILE_LIMIT);
1197   /* if faststart, update the offset of the atoms in the movie with the offset
1198    * that the movie headers before mdat will cause.
1199    * Also, send the ftyp */
1200   if (qtmux->fast_start_file) {
1201     GstFlowReturn flow_ret;
1202     offset = size = 0;
1203
1204     flow_ret = gst_qt_mux_prepare_and_send_ftyp (qtmux);
1205     if (flow_ret != GST_FLOW_OK) {
1206       goto ftyp_error;
1207     }
1208     /* copy into NULL to obtain size */
1209     if (!atom_moov_copy_data (qtmux->moov, NULL, &size, &offset))
1210       goto serialize_error;
1211     GST_DEBUG_OBJECT (qtmux, "calculated moov atom size %" G_GUINT64_FORMAT,
1212         offset);
1213     offset += qtmux->header_size + (large_file ? 16 : 8);
1214   } else
1215     offset = qtmux->header_size;
1216   atom_moov_chunks_add_offset (qtmux->moov, offset);
1217
1218   /* serialize moov */
1219   offset = size = 0;
1220   data = NULL;
1221   GST_LOG_OBJECT (qtmux, "Copying movie header into buffer");
1222   ret = atom_moov_copy_data (qtmux->moov, &data, &size, &offset);
1223   if (!ret)
1224     goto serialize_error;
1225
1226   buffer = gst_buffer_new ();
1227   GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer) = data;
1228   GST_BUFFER_SIZE (buffer) = offset;
1229   /* note: as of this point, we no longer care about tracking written data size,
1230    * since there is no more use for it anyway */
1231   GST_DEBUG_OBJECT (qtmux, "Pushing movie atoms");
1232   gst_qt_mux_send_buffer (qtmux, buffer, NULL, FALSE);
1233
1234   /* if needed, send mdat atom and move buffered data into it */
1235   if (qtmux->fast_start_file) {
1236     /* mdat size = accumulated (buffered data) + mdat atom header */
1237     ret = gst_qt_mux_send_mdat_header (qtmux, NULL, qtmux->mdat_size,
1238         large_file);
1239     if (ret != GST_FLOW_OK)
1240       return ret;
1241     ret = gst_qt_mux_send_buffered_data (qtmux, NULL);
1242     if (ret != GST_FLOW_OK)
1243       return ret;
1244   } else {
1245     /* mdata needs update iff not using faststart */
1246     GST_DEBUG_OBJECT (qtmux, "updating mdata size");
1247     ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
1248         qtmux->mdat_size, NULL);
1249     /* note; no seeking back to the end of file is done,
1250      * since we longer write anything anyway */
1251   }
1252
1253   return ret;
1254
1255   /* ERRORS */
1256 serialize_error:
1257   {
1258     gst_buffer_unref (buffer);
1259     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1260         ("Failed to serialize moov"));
1261     return GST_FLOW_ERROR;
1262   }
1263 ftyp_error:
1264   {
1265     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL), ("Failed to send ftyp"));
1266     return GST_FLOW_ERROR;
1267   }
1268 }
1269
1270 /*
1271  * Here we push the buffer and update the tables in the track atoms
1272  */
1273 static GstFlowReturn
1274 gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
1275 {
1276   GstBuffer *last_buf = NULL;
1277   GstClockTime duration;
1278   guint nsamples, sample_size;
1279   guint64 scaled_duration, chunk_offset;
1280   gint64 last_dts;
1281   gint64 pts_offset = 0;
1282   gboolean sync = FALSE, do_pts = FALSE;
1283
1284   if (!pad->fourcc)
1285     goto not_negotiated;
1286
1287   last_buf = pad->last_buf;
1288   if (last_buf == NULL) {
1289 #ifndef GST_DISABLE_GST_DEBUG
1290     if (buf == NULL) {
1291       GST_DEBUG_OBJECT (qtmux, "Pad %s has no previous buffer stored and "
1292           "received NULL buffer, doing nothing",
1293           GST_PAD_NAME (pad->collect.pad));
1294     } else {
1295       GST_LOG_OBJECT (qtmux,
1296           "Pad %s has no previous buffer stored, storing now",
1297           GST_PAD_NAME (pad->collect.pad));
1298     }
1299 #endif
1300     pad->last_buf = buf;
1301     return GST_FLOW_OK;
1302   } else
1303     gst_buffer_ref (last_buf);
1304
1305   /* fall back to duration if:
1306    * - last bufer
1307    * - this format has out of order buffers (e.g. MPEG-4),
1308    * - lack of valid time forces fall back */
1309   if (buf == NULL || pad->is_out_of_order ||
1310       !GST_BUFFER_TIMESTAMP_IS_VALID (last_buf) ||
1311       !GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
1312     if (!GST_BUFFER_DURATION_IS_VALID (last_buf)) {
1313       /* be forgiving for some possibly last upstream flushed buffer */
1314       if (buf)
1315         goto no_time;
1316       GST_WARNING_OBJECT (qtmux, "no duration for last buffer");
1317       /* iso spec recommends some small value, try 0 */
1318       duration = 0;
1319     } else {
1320       duration = GST_BUFFER_DURATION (last_buf);
1321     }
1322   } else {
1323     duration = GST_BUFFER_TIMESTAMP (buf) - GST_BUFFER_TIMESTAMP (last_buf);
1324   }
1325
1326   gst_buffer_replace (&pad->last_buf, buf);
1327
1328   last_dts = gst_util_uint64_scale (pad->last_dts,
1329       atom_trak_get_timescale (pad->trak), GST_SECOND);
1330
1331   /* raw audio has many samples per buffer (= chunk) */
1332   if (pad->sample_size) {
1333     sample_size = pad->sample_size;
1334     if (GST_BUFFER_SIZE (last_buf) % sample_size != 0)
1335       goto fragmented_sample;
1336     /* note: qt raw audio storage warps it implicitly into a timewise
1337      * perfect stream, discarding buffer times */
1338     nsamples = GST_BUFFER_SIZE (last_buf) / sample_size;
1339     duration = GST_BUFFER_DURATION (last_buf) / nsamples;
1340     /* timescale = samplerate */
1341     scaled_duration = 1;
1342     pad->last_dts += duration * nsamples;
1343   } else {
1344     nsamples = 1;
1345     sample_size = GST_BUFFER_SIZE (last_buf);
1346     if (pad->have_dts) {
1347       gint64 scaled_dts;
1348       pad->last_dts = GST_BUFFER_OFFSET_END (last_buf);
1349       if ((gint64) (pad->last_dts) < 0) {
1350         scaled_dts = -gst_util_uint64_scale (-pad->last_dts,
1351             atom_trak_get_timescale (pad->trak), GST_SECOND);
1352       } else {
1353         scaled_dts = gst_util_uint64_scale (pad->last_dts,
1354             atom_trak_get_timescale (pad->trak), GST_SECOND);
1355       }
1356       scaled_duration = scaled_dts - last_dts;
1357       last_dts = scaled_dts;
1358     } else {
1359       /* first convert intended timestamp (in GstClockTime resolution) to
1360        * trak timescale, then derive delta;
1361        * this ensures sums of (scale)delta add up to converted timestamp,
1362        * which only deviates at most 1/scale from timestamp itself */
1363       scaled_duration = gst_util_uint64_scale (pad->last_dts + duration,
1364           atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts;
1365       pad->last_dts += duration;
1366     }
1367   }
1368   chunk_offset = qtmux->mdat_size;
1369
1370   GST_LOG_OBJECT (qtmux,
1371       "Pad (%s) dts updated to %" GST_TIME_FORMAT,
1372       GST_PAD_NAME (pad->collect.pad), GST_TIME_ARGS (pad->last_dts));
1373   GST_LOG_OBJECT (qtmux,
1374       "Adding %d samples to track, duration: %" G_GUINT64_FORMAT
1375       " size: %" G_GUINT32_FORMAT " chunk offset: %" G_GUINT64_FORMAT,
1376       nsamples, scaled_duration, sample_size, chunk_offset);
1377
1378   /* might be a sync sample */
1379   if (pad->sync &&
1380       !GST_BUFFER_FLAG_IS_SET (last_buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
1381     GST_LOG_OBJECT (qtmux, "Adding new sync sample entry for track of pad %s",
1382         GST_PAD_NAME (pad->collect.pad));
1383     sync = TRUE;
1384   }
1385
1386   /* optionally calculate ctts entry values
1387    * (if composition-time expected different from decoding-time) */
1388   /* really not recommended:
1389    * - decoder typically takes care of dts/pts issues
1390    * - in case of out-of-order, dts may only be determined as above
1391    *   (e.g. sum of duration), which may be totally different from
1392    *   buffer timestamps in case of multiple segment, non-perfect streams
1393    *  (and just perhaps maybe with some luck segment_to_running_time
1394    *   or segment_to_media_time might get near to it) */
1395   if ((pad->have_dts || qtmux->guess_pts) && pad->is_out_of_order) {
1396     guint64 pts;
1397
1398     pts = gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (last_buf),
1399         atom_trak_get_timescale (pad->trak), GST_SECOND);
1400     pts_offset = (gint64) (pts - last_dts);
1401     do_pts = TRUE;
1402     GST_LOG_OBJECT (qtmux, "Adding ctts entry for pad %s: %" G_GINT64_FORMAT,
1403         GST_PAD_NAME (pad->collect.pad), pts_offset);
1404   }
1405
1406   /*
1407    * Each buffer starts a new chunk, so we can assume the buffer
1408    * duration is the chunk duration
1409    */
1410   if (GST_CLOCK_TIME_IS_VALID (duration) && (duration > qtmux->longest_chunk ||
1411           !GST_CLOCK_TIME_IS_VALID (qtmux->longest_chunk))) {
1412     GST_DEBUG_OBJECT (qtmux, "New longest chunk found: %" GST_TIME_FORMAT
1413         ", pad %s", GST_TIME_ARGS (duration), GST_PAD_NAME (pad->collect.pad));
1414     qtmux->longest_chunk = duration;
1415   }
1416
1417   /* if this is the first buffer, store the timestamp */
1418   if (G_UNLIKELY (pad->first_ts == GST_CLOCK_TIME_NONE) && last_buf) {
1419     if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (last_buf))) {
1420       pad->first_ts = GST_BUFFER_TIMESTAMP (last_buf);
1421     } else {
1422       GST_DEBUG_OBJECT (qtmux, "First buffer for pad %s has no timestamp, "
1423           "using 0 as first timestamp");
1424       pad->first_ts = 0;
1425     }
1426     GST_DEBUG_OBJECT (qtmux, "Stored first timestamp for pad %s %"
1427         GST_TIME_FORMAT, GST_PAD_NAME (pad->collect.pad),
1428         GST_TIME_ARGS (pad->first_ts));
1429   }
1430
1431   /* now we go and register this buffer/sample all over */
1432   /* note that a new chunk is started each time (not fancy but works) */
1433   atom_trak_add_samples (pad->trak, nsamples, scaled_duration, sample_size,
1434       chunk_offset, sync, do_pts, pts_offset);
1435
1436   if (buf)
1437     gst_buffer_unref (buf);
1438
1439   return gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE);
1440
1441   /* ERRORS */
1442 bail:
1443   {
1444     if (buf)
1445       gst_buffer_unref (buf);
1446     gst_buffer_unref (last_buf);
1447     return GST_FLOW_ERROR;
1448   }
1449 no_time:
1450   {
1451     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1452         ("Received buffer without timestamp/duration."));
1453     goto bail;
1454   }
1455 fragmented_sample:
1456   {
1457     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1458         ("Audio buffer contains fragmented sample."));
1459     goto bail;
1460   }
1461 not_negotiated:
1462   {
1463     GST_ELEMENT_ERROR (qtmux, CORE, NEGOTIATION, (NULL),
1464         ("format wasn't negotiated before buffer flow on pad %s",
1465             GST_PAD_NAME (pad->collect.pad)));
1466     if (buf)
1467       gst_buffer_unref (buf);
1468     return GST_FLOW_NOT_NEGOTIATED;
1469   }
1470 }
1471
1472 static GstFlowReturn
1473 gst_qt_mux_collected (GstCollectPads * pads, gpointer user_data)
1474 {
1475   GstFlowReturn ret = GST_FLOW_OK;
1476   GstQTMux *qtmux = GST_QT_MUX_CAST (user_data);
1477   GSList *walk;
1478   GstQTPad *best_pad = NULL;
1479   GstClockTime time, best_time = GST_CLOCK_TIME_NONE;
1480   GstBuffer *buf;
1481
1482   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_STARTED)) {
1483     if ((ret = gst_qt_mux_start_file (qtmux)) != GST_FLOW_OK)
1484       return ret;
1485     else
1486       qtmux->state = GST_QT_MUX_STATE_DATA;
1487   }
1488
1489   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS))
1490     return GST_FLOW_UNEXPECTED;
1491
1492   /* select the best buffer */
1493   walk = qtmux->collect->data;
1494   while (walk) {
1495     GstQTPad *pad;
1496     GstCollectData *data;
1497
1498     data = (GstCollectData *) walk->data;
1499     pad = (GstQTPad *) data;
1500
1501     walk = g_slist_next (walk);
1502
1503     buf = gst_collect_pads_peek (pads, data);
1504     if (buf == NULL) {
1505       GST_LOG_OBJECT (qtmux, "Pad %s has no buffers",
1506           GST_PAD_NAME (pad->collect.pad));
1507       continue;
1508     }
1509     time = GST_BUFFER_TIMESTAMP (buf);
1510     gst_buffer_unref (buf);
1511
1512     if (best_pad == NULL || !GST_CLOCK_TIME_IS_VALID (time) ||
1513         (GST_CLOCK_TIME_IS_VALID (best_time) && time < best_time)) {
1514       best_pad = pad;
1515       best_time = time;
1516     }
1517   }
1518
1519   if (best_pad != NULL) {
1520     GST_LOG_OBJECT (qtmux, "selected pad %s with time %" GST_TIME_FORMAT,
1521         GST_PAD_NAME (best_pad->collect.pad), GST_TIME_ARGS (best_time));
1522     buf = gst_collect_pads_pop (pads, &best_pad->collect);
1523     ret = gst_qt_mux_add_buffer (qtmux, best_pad, buf);
1524   } else {
1525     ret = gst_qt_mux_stop_file (qtmux);
1526     if (ret == GST_FLOW_OK) {
1527       gst_pad_push_event (qtmux->srcpad, gst_event_new_eos ());
1528       ret = GST_FLOW_UNEXPECTED;
1529     }
1530     qtmux->state = GST_QT_MUX_STATE_EOS;
1531   }
1532
1533   return ret;
1534 }
1535
1536 static gboolean
1537 gst_qt_mux_audio_sink_set_caps (GstPad * pad, GstCaps * caps)
1538 {
1539   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1540   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1541   GstQTPad *qtpad = NULL;
1542   GstStructure *structure;
1543   const gchar *mimetype;
1544   gint rate, channels;
1545   const GValue *value = NULL;
1546   const GstBuffer *codec_data = NULL;
1547   GstQTMuxFormat format;
1548   AudioSampleEntry entry = { 0, };
1549   AtomInfo *ext_atom = NULL;
1550   gint constant_size = 0;
1551
1552   /* find stream data */
1553   qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
1554   g_assert (qtpad);
1555
1556   /* does not go well to renegotiate stream mid-way */
1557   if (qtpad->fourcc)
1558     goto refuse_renegotiation;
1559
1560   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
1561       GST_DEBUG_PAD_NAME (pad), caps);
1562
1563   format = qtmux_klass->format;
1564   structure = gst_caps_get_structure (caps, 0);
1565   mimetype = gst_structure_get_name (structure);
1566
1567   /* common info */
1568   if (!gst_structure_get_int (structure, "channels", &channels) ||
1569       !gst_structure_get_int (structure, "rate", &rate)) {
1570     goto refuse_caps;
1571   }
1572
1573   /* optional */
1574   value = gst_structure_get_value (structure, "codec_data");
1575   if (value != NULL)
1576     codec_data = gst_value_get_buffer (value);
1577
1578   qtpad->is_out_of_order = FALSE;
1579   qtpad->have_dts = FALSE;
1580
1581   /* set common properties */
1582   entry.sample_rate = rate;
1583   entry.channels = channels;
1584   /* default */
1585   entry.sample_size = 16;
1586   /* this is the typical compressed case */
1587   if (format == GST_QT_MUX_FORMAT_QT) {
1588     entry.version = 1;
1589     entry.compression_id = -2;
1590   }
1591
1592   /* now map onto a fourcc, and some extra properties */
1593   if (strcmp (mimetype, "audio/mpeg") == 0) {
1594     gint mpegversion = 0;
1595     gint layer = -1;
1596
1597     gst_structure_get_int (structure, "mpegversion", &mpegversion);
1598     switch (mpegversion) {
1599       case 1:
1600         gst_structure_get_int (structure, "layer", &layer);
1601         switch (layer) {
1602           case 3:
1603             /* mp3 */
1604             /* note: QuickTime player does not like mp3 either way in iso/mp4 */
1605             if (format == GST_QT_MUX_FORMAT_QT)
1606               entry.fourcc = FOURCC__mp3;
1607             else {
1608               entry.fourcc = FOURCC_mp4a;
1609               ext_atom =
1610                   build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG1_P3,
1611                   ESDS_STREAM_TYPE_AUDIO, codec_data);
1612             }
1613             entry.samples_per_packet = 1152;
1614             entry.bytes_per_sample = 2;
1615             break;
1616         }
1617         break;
1618       case 4:
1619         /* AAC */
1620         entry.fourcc = FOURCC_mp4a;
1621         if (!codec_data || GST_BUFFER_SIZE (codec_data) < 2)
1622           GST_WARNING_OBJECT (qtmux, "no (valid) codec_data for AAC audio");
1623         else {
1624           guint8 profile = GST_READ_UINT8 (GST_BUFFER_DATA (codec_data));
1625
1626           /* warn if not Low Complexity profile */
1627           profile >>= 3;
1628           if (profile != 2)
1629             GST_WARNING_OBJECT (qtmux,
1630                 "non-LC AAC may not run well on (Apple) QuickTime/iTunes");
1631         }
1632         if (format == GST_QT_MUX_FORMAT_QT)
1633           ext_atom = build_mov_aac_extension (qtpad->trak, codec_data);
1634         else
1635           ext_atom =
1636               build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P3,
1637               ESDS_STREAM_TYPE_AUDIO, codec_data);
1638         break;
1639       default:
1640         break;
1641     }
1642   } else if (strcmp (mimetype, "audio/AMR") == 0) {
1643     entry.fourcc = FOURCC_samr;
1644     entry.sample_size = 16;
1645     entry.samples_per_packet = 160;
1646     entry.bytes_per_sample = 2;
1647     ext_atom = build_amr_extension ();
1648   } else if (strcmp (mimetype, "audio/AMR-WB") == 0) {
1649     entry.fourcc = FOURCC_sawb;
1650     entry.sample_size = 16;
1651     entry.samples_per_packet = 320;
1652     entry.bytes_per_sample = 2;
1653     ext_atom = build_amr_extension ();
1654   } else if (strcmp (mimetype, "audio/x-raw-int") == 0) {
1655     gint width;
1656     gint depth;
1657     gint endianness;
1658     gboolean sign;
1659
1660     if (!gst_structure_get_int (structure, "width", &width) ||
1661         !gst_structure_get_int (structure, "depth", &depth) ||
1662         !gst_structure_get_boolean (structure, "signed", &sign)) {
1663       GST_DEBUG_OBJECT (qtmux, "broken caps, width/depth/signed field missing");
1664       goto refuse_caps;
1665     }
1666
1667     if (depth <= 8) {
1668       endianness = G_BYTE_ORDER;
1669     } else if (!gst_structure_get_boolean (structure,
1670             "endianness", &endianness)) {
1671       GST_DEBUG_OBJECT (qtmux, "broken caps, endianness field missing");
1672       goto refuse_caps;
1673     }
1674
1675     /* spec has no place for a distinction in these */
1676     if (width != depth) {
1677       GST_DEBUG_OBJECT (qtmux, "width must be same as depth!");
1678       goto refuse_caps;
1679     }
1680
1681     if (sign) {
1682       if (endianness == G_LITTLE_ENDIAN)
1683         entry.fourcc = FOURCC_sowt;
1684       else if (endianness == G_BIG_ENDIAN)
1685         entry.fourcc = FOURCC_twos;
1686       /* maximum backward compatibility; only new version for > 16 bit */
1687       if (depth <= 16)
1688         entry.version = 0;
1689       /* not compressed in any case */
1690       entry.compression_id = 0;
1691       /* QT spec says: max at 16 bit even if sample size were actually larger,
1692        * however, most players (e.g. QuickTime!) seem to disagree, so ... */
1693       entry.sample_size = depth;
1694       entry.bytes_per_sample = depth / 8;
1695       entry.samples_per_packet = 1;
1696       entry.bytes_per_packet = depth / 8;
1697       entry.bytes_per_frame = entry.bytes_per_packet * channels;
1698     } else {
1699       if (width == 8 && depth == 8) {
1700         /* fall back to old 8-bit version */
1701         entry.fourcc = FOURCC_raw_;
1702         entry.version = 0;
1703         entry.compression_id = 0;
1704         entry.sample_size = 8;
1705       } else {
1706         GST_DEBUG_OBJECT (qtmux, "non 8-bit PCM must be signed");
1707         goto refuse_caps;
1708       }
1709     }
1710     constant_size = (depth / 8) * channels;
1711   } else if (strcmp (mimetype, "audio/x-alaw") == 0) {
1712     entry.fourcc = FOURCC_alaw;
1713     entry.samples_per_packet = 1023;
1714     entry.bytes_per_sample = 2;
1715   } else if (strcmp (mimetype, "audio/x-mulaw") == 0) {
1716     entry.fourcc = FOURCC_ulaw;
1717     entry.samples_per_packet = 1023;
1718     entry.bytes_per_sample = 2;
1719   }
1720
1721   if (!entry.fourcc)
1722     goto refuse_caps;
1723
1724   /* ok, set the pad info accordingly */
1725   qtpad->fourcc = entry.fourcc;
1726   qtpad->sample_size = constant_size;
1727   atom_trak_set_audio_type (qtpad->trak, qtmux->context, &entry,
1728       entry.sample_rate, ext_atom, constant_size);
1729
1730   gst_object_unref (qtmux);
1731   return TRUE;
1732
1733   /* ERRORS */
1734 refuse_caps:
1735   {
1736     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
1737         GST_PAD_NAME (pad), caps);
1738     gst_object_unref (qtmux);
1739     return FALSE;
1740   }
1741 refuse_renegotiation:
1742   {
1743     GST_WARNING_OBJECT (qtmux,
1744         "pad %s refused renegotiation to %" GST_PTR_FORMAT,
1745         GST_PAD_NAME (pad), caps);
1746     gst_object_unref (qtmux);
1747     return FALSE;
1748   }
1749 }
1750
1751 /* scale rate up or down by factor of 10 to fit into [1000,10000] interval */
1752 static guint32
1753 adjust_rate (guint64 rate)
1754 {
1755   while (rate >= 10000)
1756     rate /= 10;
1757
1758   while (rate < 1000)
1759     rate *= 10;
1760
1761   return (guint32) rate;
1762 }
1763
1764 static gboolean
1765 gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps)
1766 {
1767   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1768   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1769   GstQTPad *qtpad = NULL;
1770   GstStructure *structure;
1771   const gchar *mimetype;
1772   gint width, height, depth = -1;
1773   gint framerate_num, framerate_den;
1774   guint32 rate;
1775   const GValue *value = NULL;
1776   const GstBuffer *codec_data = NULL;
1777   VisualSampleEntry entry = { 0, };
1778   GstQTMuxFormat format;
1779   AtomInfo *ext_atom = NULL;
1780   GList *ext_atom_list = NULL;
1781   gboolean sync = FALSE;
1782   int par_num, par_den;
1783
1784   /* find stream data */
1785   qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
1786   g_assert (qtpad);
1787
1788   /* does not go well to renegotiate stream mid-way */
1789   if (qtpad->fourcc)
1790     goto refuse_renegotiation;
1791
1792   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
1793       GST_DEBUG_PAD_NAME (pad), caps);
1794
1795   format = qtmux_klass->format;
1796   structure = gst_caps_get_structure (caps, 0);
1797   mimetype = gst_structure_get_name (structure);
1798
1799   /* required parts */
1800   if (!gst_structure_get_int (structure, "width", &width) ||
1801       !gst_structure_get_int (structure, "height", &height))
1802     goto refuse_caps;
1803
1804   /* optional */
1805   depth = -1;
1806   /* works as a default timebase */
1807   framerate_num = 10000;
1808   framerate_den = 1;
1809   gst_structure_get_fraction (structure, "framerate", &framerate_num,
1810       &framerate_den);
1811   gst_structure_get_int (structure, "depth", &depth);
1812   value = gst_structure_get_value (structure, "codec_data");
1813   if (value != NULL)
1814     codec_data = gst_value_get_buffer (value);
1815
1816   par_num = 1;
1817   par_den = 1;
1818   gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_num,
1819       &par_den);
1820
1821   qtpad->is_out_of_order = FALSE;
1822
1823   /* bring frame numerator into a range that ensures both reasonable resolution
1824    * as well as a fair duration */
1825   rate = adjust_rate (framerate_num);
1826   GST_DEBUG_OBJECT (qtmux, "Rate of video track selected: %" G_GUINT32_FORMAT,
1827       rate);
1828
1829   /* set common properties */
1830   entry.width = width;
1831   entry.height = height;
1832   entry.par_n = par_num;
1833   entry.par_d = par_den;
1834   /* should be OK according to qt and iso spec, override if really needed */
1835   entry.color_table_id = -1;
1836   entry.frame_count = 1;
1837   entry.depth = 24;
1838
1839   /* sync entries by default */
1840   sync = TRUE;
1841
1842   /* now map onto a fourcc, and some extra properties */
1843   if (strcmp (mimetype, "video/x-raw-rgb") == 0) {
1844     gint bpp;
1845
1846     entry.fourcc = FOURCC_raw_;
1847     gst_structure_get_int (structure, "bpp", &bpp);
1848     entry.depth = bpp;
1849     sync = FALSE;
1850   } else if (strcmp (mimetype, "video/x-raw-yuv") == 0) {
1851     guint32 format = 0;
1852
1853     sync = FALSE;
1854     gst_structure_get_fourcc (structure, "format", &format);
1855     switch (format) {
1856       case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
1857         if (depth == -1)
1858           depth = 24;
1859         entry.fourcc = FOURCC_2vuy;
1860         entry.depth = depth;
1861         break;
1862     }
1863   } else if (strcmp (mimetype, "video/x-h263") == 0) {
1864     ext_atom = NULL;
1865     if (format == GST_QT_MUX_FORMAT_QT)
1866       entry.fourcc = FOURCC_h263;
1867     else
1868       entry.fourcc = FOURCC_s263;
1869     ext_atom = build_h263_extension ();
1870     if (ext_atom != NULL)
1871       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
1872   } else if (strcmp (mimetype, "video/x-divx") == 0 ||
1873       strcmp (mimetype, "video/mpeg") == 0) {
1874     gint version = 0;
1875
1876     if (strcmp (mimetype, "video/x-divx") == 0) {
1877       gst_structure_get_int (structure, "divxversion", &version);
1878       version = version == 5 ? 1 : 0;
1879     } else {
1880       gst_structure_get_int (structure, "mpegversion", &version);
1881       version = version == 4 ? 1 : 0;
1882     }
1883     if (version) {
1884       entry.fourcc = FOURCC_mp4v;
1885       ext_atom =
1886           build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P2,
1887           ESDS_STREAM_TYPE_VISUAL, codec_data);
1888       if (ext_atom != NULL)
1889         ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
1890       if (!codec_data)
1891         GST_WARNING_OBJECT (qtmux, "no codec_data for MPEG4 video; "
1892             "output might not play in Apple QuickTime (try global-headers?)");
1893     }
1894   } else if (strcmp (mimetype, "video/x-h264") == 0) {
1895     entry.fourcc = FOURCC_avc1;
1896     qtpad->is_out_of_order = TRUE;
1897     if (!codec_data)
1898       GST_WARNING_OBJECT (qtmux, "no codec_data in h264 caps");
1899     ext_atom = build_codec_data_extension (FOURCC_avcC, codec_data);
1900     if (ext_atom != NULL)
1901       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
1902   } else if (strcmp (mimetype, "video/x-svq") == 0) {
1903     gint version = 0;
1904     const GstBuffer *seqh = NULL;
1905     const GValue *seqh_value;
1906     gdouble gamma = 0;
1907
1908     gst_structure_get_int (structure, "svqversion", &version);
1909     if (version == 3) {
1910       entry.fourcc = FOURCC_SVQ3;
1911       entry.version = 3;
1912       entry.depth = 32;
1913       qtpad->is_out_of_order = TRUE;
1914
1915       seqh_value = gst_structure_get_value (structure, "seqh");
1916       if (seqh_value) {
1917         seqh = gst_value_get_buffer (seqh_value);
1918         ext_atom = build_SMI_atom (seqh);
1919         if (ext_atom)
1920           ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
1921       }
1922
1923       /* we need to add the gamma anyway because quicktime might crash
1924        * when it doesn't find it */
1925       if (!gst_structure_get_double (structure, "applied-gamma", &gamma)) {
1926         /* it seems that using 0 here makes it ignored */
1927         gamma = 0.0;
1928       }
1929       ext_atom = build_gama_atom (gamma);
1930       if (ext_atom)
1931         ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
1932     } else {
1933       GST_WARNING_OBJECT (qtmux, "SVQ version %d not supported. Please file "
1934           "a bug at http://bugzilla.gnome.org", version);
1935     }
1936   } else if (strcmp (mimetype, "video/x-dv") == 0) {
1937     gint version = 0;
1938     gboolean pal = TRUE;
1939
1940     sync = FALSE;
1941     if (framerate_num != 25 || framerate_den != 1)
1942       pal = FALSE;
1943     gst_structure_get_int (structure, "dvversion", &version);
1944     /* fall back to typical one */
1945     if (!version)
1946       version = 25;
1947     switch (version) {
1948       case 25:
1949         if (pal)
1950           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', 'p');
1951         else
1952           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', ' ');
1953         break;
1954       case 50:
1955         if (pal)
1956           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'p');
1957         else
1958           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'n');
1959         break;
1960       default:
1961         GST_WARNING_OBJECT (qtmux, "unrecognized dv version");
1962         break;
1963     }
1964   } else if (strcmp (mimetype, "image/jpeg") == 0) {
1965     entry.fourcc = FOURCC_jpeg;
1966     sync = FALSE;
1967   } else if (strcmp (mimetype, "image/x-j2c") == 0) {
1968     guint32 fourcc;
1969
1970     ext_atom = NULL;
1971     entry.fourcc = FOURCC_mjp2;
1972     sync = FALSE;
1973     if (!gst_structure_get_fourcc (structure, "fourcc", &fourcc) ||
1974         !(ext_atom =
1975             build_jp2h_extension (qtpad->trak, width, height, fourcc))) {
1976       GST_DEBUG_OBJECT (qtmux, "missing or invalid fourcc in jp2 caps");
1977       goto refuse_caps;
1978     }
1979     if (ext_atom)
1980       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
1981   } else if (strcmp (mimetype, "video/x-qt-part") == 0) {
1982     guint32 fourcc;
1983
1984     gst_structure_get_fourcc (structure, "format", &fourcc);
1985     entry.fourcc = fourcc;
1986     qtpad->is_out_of_order = TRUE;
1987     qtpad->have_dts = TRUE;
1988   } else if (strcmp (mimetype, "video/x-mp4-part") == 0) {
1989     guint32 fourcc;
1990
1991     gst_structure_get_fourcc (structure, "format", &fourcc);
1992     entry.fourcc = fourcc;
1993     qtpad->is_out_of_order = TRUE;
1994     qtpad->have_dts = TRUE;
1995   }
1996
1997   if (!entry.fourcc)
1998     goto refuse_caps;
1999
2000   /* ok, set the pad info accordingly */
2001   qtpad->fourcc = entry.fourcc;
2002   qtpad->sync = sync;
2003   atom_trak_set_video_type (qtpad->trak, qtmux->context, &entry, rate,
2004       ext_atom_list);
2005
2006   gst_object_unref (qtmux);
2007   return TRUE;
2008
2009   /* ERRORS */
2010 refuse_caps:
2011   {
2012     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
2013         GST_PAD_NAME (pad), caps);
2014     gst_object_unref (qtmux);
2015     return FALSE;
2016   }
2017 refuse_renegotiation:
2018   {
2019     GST_WARNING_OBJECT (qtmux,
2020         "pad %s refused renegotiation to %" GST_PTR_FORMAT " from %"
2021         GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, GST_PAD_CAPS (pad));
2022     gst_object_unref (qtmux);
2023     return FALSE;
2024   }
2025 }
2026
2027 static gboolean
2028 gst_qt_mux_sink_event (GstPad * pad, GstEvent * event)
2029 {
2030   gboolean ret;
2031   GstQTMux *qtmux;
2032
2033   qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
2034   switch (GST_EVENT_TYPE (event)) {
2035     case GST_EVENT_TAG:{
2036       GstTagList *list;
2037       GstTagSetter *setter = GST_TAG_SETTER (qtmux);
2038       const GstTagMergeMode mode = gst_tag_setter_get_tag_merge_mode (setter);
2039
2040       GST_DEBUG_OBJECT (qtmux, "received tag event");
2041       gst_event_parse_tag (event, &list);
2042       gst_tag_setter_merge_tags (setter, list, mode);
2043       break;
2044     }
2045     default:
2046       break;
2047   }
2048
2049   ret = qtmux->collect_event (pad, event);
2050   gst_object_unref (qtmux);
2051
2052   return ret;
2053 }
2054
2055 static void
2056 gst_qt_mux_release_pad (GstElement * element, GstPad * pad)
2057 {
2058   GstQTMux *mux = GST_QT_MUX_CAST (element);
2059
2060   /* let GstCollectPads complain if it is some unknown pad */
2061   if (gst_collect_pads_remove_pad (mux->collect, pad))
2062     gst_element_remove_pad (element, pad);
2063 }
2064
2065 static GstPad *
2066 gst_qt_mux_request_new_pad (GstElement * element,
2067     GstPadTemplate * templ, const gchar * name)
2068 {
2069   GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
2070   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
2071   GstQTPad *collect_pad;
2072   GstPad *newpad;
2073   gboolean audio;
2074
2075   GST_DEBUG_OBJECT (qtmux, "Requested pad: %s", GST_STR_NULL (name));
2076
2077   if (qtmux->state != GST_QT_MUX_STATE_NONE) {
2078     GST_WARNING_OBJECT (qtmux, "Not providing request pad after stream start.");
2079     return NULL;
2080   }
2081
2082   if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
2083     audio = TRUE;
2084   } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
2085     audio = FALSE;
2086   } else {
2087     GST_WARNING_OBJECT (qtmux, "This is not our template!");
2088     return NULL;
2089   }
2090
2091   /* add pad to collections */
2092   newpad = gst_pad_new_from_template (templ, name);
2093   collect_pad = (GstQTPad *)
2094       gst_collect_pads_add_pad_full (qtmux->collect, newpad, sizeof (GstQTPad),
2095       (GstCollectDataDestroyNotify) (gst_qt_mux_pad_reset));
2096   /* set up pad */
2097   gst_qt_mux_pad_reset (collect_pad);
2098   collect_pad->trak = atom_trak_new (qtmux->context);
2099   atom_moov_add_trak (qtmux->moov, collect_pad->trak);
2100
2101   /* set up pad functions */
2102   if (audio)
2103     gst_pad_set_setcaps_function (newpad,
2104         GST_DEBUG_FUNCPTR (gst_qt_mux_audio_sink_set_caps));
2105   else
2106     gst_pad_set_setcaps_function (newpad,
2107         GST_DEBUG_FUNCPTR (gst_qt_mux_video_sink_set_caps));
2108
2109   /* FIXME: hacked way to override/extend the event function of
2110    * GstCollectPads; because it sets its own event function giving the
2111    * element no access to events.
2112    */
2113   qtmux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
2114   gst_pad_set_event_function (newpad,
2115       GST_DEBUG_FUNCPTR (gst_qt_mux_sink_event));
2116
2117   gst_pad_set_active (newpad, TRUE);
2118   gst_element_add_pad (element, newpad);
2119
2120   return newpad;
2121 }
2122
2123 static void
2124 gst_qt_mux_get_property (GObject * object,
2125     guint prop_id, GValue * value, GParamSpec * pspec)
2126 {
2127   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
2128
2129   GST_OBJECT_LOCK (qtmux);
2130   switch (prop_id) {
2131     case PROP_LARGE_FILE:
2132       g_value_set_boolean (value, qtmux->large_file);
2133       break;
2134     case PROP_MOVIE_TIMESCALE:
2135       g_value_set_uint (value, qtmux->timescale);
2136       break;
2137     case PROP_DO_CTTS:
2138       g_value_set_boolean (value, qtmux->guess_pts);
2139       break;
2140     case PROP_FAST_START:
2141       g_value_set_boolean (value, qtmux->fast_start);
2142       break;
2143     case PROP_FAST_START_TEMP_FILE:
2144       g_value_set_string (value, qtmux->fast_start_file_path);
2145       break;
2146     default:
2147       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2148       break;
2149   }
2150   GST_OBJECT_UNLOCK (qtmux);
2151 }
2152
2153 static void
2154 gst_qt_mux_generate_fast_start_file_path (GstQTMux * qtmux)
2155 {
2156   gchar *tmp;
2157
2158   if (qtmux->fast_start_file_path) {
2159     g_free (qtmux->fast_start_file_path);
2160     qtmux->fast_start_file_path = NULL;
2161   }
2162
2163   tmp = g_strdup_printf ("%s%d", "qtmux", g_random_int ());
2164   qtmux->fast_start_file_path = g_build_filename (g_get_tmp_dir (), tmp, NULL);
2165   g_free (tmp);
2166 }
2167
2168 static void
2169 gst_qt_mux_set_property (GObject * object,
2170     guint prop_id, const GValue * value, GParamSpec * pspec)
2171 {
2172   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
2173
2174   GST_OBJECT_LOCK (qtmux);
2175   switch (prop_id) {
2176     case PROP_LARGE_FILE:
2177       qtmux->large_file = g_value_get_boolean (value);
2178       break;
2179     case PROP_MOVIE_TIMESCALE:
2180       qtmux->timescale = g_value_get_uint (value);
2181       break;
2182     case PROP_DO_CTTS:
2183       qtmux->guess_pts = g_value_get_boolean (value);
2184       break;
2185     case PROP_FAST_START:
2186       qtmux->fast_start = g_value_get_boolean (value);
2187       break;
2188     case PROP_FAST_START_TEMP_FILE:
2189       if (qtmux->fast_start_file_path) {
2190         g_free (qtmux->fast_start_file_path);
2191       }
2192       qtmux->fast_start_file_path = g_value_dup_string (value);
2193       /* NULL means to generate a random one */
2194       if (!qtmux->fast_start_file_path) {
2195         gst_qt_mux_generate_fast_start_file_path (qtmux);
2196       }
2197       break;
2198     default:
2199       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2200       break;
2201   }
2202   GST_OBJECT_UNLOCK (qtmux);
2203 }
2204
2205 static GstStateChangeReturn
2206 gst_qt_mux_change_state (GstElement * element, GstStateChange transition)
2207 {
2208   GstStateChangeReturn ret;
2209   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
2210
2211   switch (transition) {
2212     case GST_STATE_CHANGE_NULL_TO_READY:
2213       break;
2214     case GST_STATE_CHANGE_READY_TO_PAUSED:
2215       gst_collect_pads_start (qtmux->collect);
2216       qtmux->state = GST_QT_MUX_STATE_STARTED;
2217       break;
2218     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
2219       break;
2220     case GST_STATE_CHANGE_PAUSED_TO_READY:
2221       gst_collect_pads_stop (qtmux->collect);
2222       break;
2223     default:
2224       break;
2225   }
2226
2227   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2228
2229   switch (transition) {
2230     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
2231       break;
2232     case GST_STATE_CHANGE_PAUSED_TO_READY:
2233       gst_qt_mux_reset (qtmux, TRUE);
2234       break;
2235     case GST_STATE_CHANGE_READY_TO_NULL:
2236       break;
2237     default:
2238       break;
2239   }
2240
2241   return ret;
2242 }
2243
2244
2245 gboolean
2246 gst_qt_mux_register (GstPlugin * plugin)
2247 {
2248   GTypeInfo typeinfo = {
2249     sizeof (GstQTMuxClass),
2250     (GBaseInitFunc) gst_qt_mux_base_init,
2251     NULL,
2252     (GClassInitFunc) gst_qt_mux_class_init,
2253     NULL,
2254     NULL,
2255     sizeof (GstQTMux),
2256     0,
2257     (GInstanceInitFunc) gst_qt_mux_init,
2258   };
2259   static const GInterfaceInfo tag_setter_info = {
2260     NULL, NULL, NULL
2261   };
2262   GType type;
2263   GstQTMuxFormat format;
2264   GstQTMuxClassParams *params;
2265   guint i = 0;
2266
2267   GST_LOG ("Registering muxers");
2268
2269   while (TRUE) {
2270     GstQTMuxFormatProp *prop;
2271
2272     prop = &gst_qt_mux_format_list[i];
2273     format = prop->format;
2274     if (format == GST_QT_MUX_FORMAT_NONE)
2275       break;
2276
2277     /* create a cache for these properties */
2278     params = g_new0 (GstQTMuxClassParams, 1);
2279     params->prop = prop;
2280     params->src_caps = gst_static_caps_get (&prop->src_caps);
2281     params->video_sink_caps = gst_static_caps_get (&prop->video_sink_caps);
2282     params->audio_sink_caps = gst_static_caps_get (&prop->audio_sink_caps);
2283
2284     /* create the type now */
2285     type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo,
2286         0);
2287     g_type_set_qdata (type, GST_QT_MUX_PARAMS_QDATA, (gpointer) params);
2288     g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
2289
2290     if (!gst_element_register (plugin, prop->name, GST_RANK_PRIMARY, type))
2291       return FALSE;
2292
2293     i++;
2294   }
2295
2296   GST_LOG ("Finished registering muxers");
2297
2298   return TRUE;
2299 }
2300
2301 gboolean
2302 gst_qt_mux_plugin_init (GstPlugin * plugin)
2303 {
2304   GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
2305
2306   return gst_qt_mux_register (plugin);
2307 }
2308
2309 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
2310     GST_VERSION_MINOR,
2311     "qtmux",
2312     "Quicktime Muxer plugin",
2313     gst_qt_mux_plugin_init, VERSION, "LGPL", "gsoc2008 package",
2314     "embedded.ufcg.edu.br")