f7aeeb50ec0ebd43add97ec8ce80c5a7c2df1521
[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    * - the buffers are out of order,
1308    * - lack of valid time forces fall back */
1309   if (buf == NULL ||
1310       !GST_BUFFER_TIMESTAMP_IS_VALID (last_buf) ||
1311       !GST_BUFFER_TIMESTAMP_IS_VALID (buf) ||
1312       GST_BUFFER_TIMESTAMP (buf) < GST_BUFFER_TIMESTAMP (last_buf)) {
1313     if (!GST_BUFFER_DURATION_IS_VALID (last_buf)) {
1314       /* be forgiving for some possibly last upstream flushed buffer */
1315       if (buf)
1316         goto no_time;
1317       GST_WARNING_OBJECT (qtmux, "no duration for last buffer");
1318       /* iso spec recommends some small value, try 0 */
1319       duration = 0;
1320     } else {
1321       duration = GST_BUFFER_DURATION (last_buf);
1322     }
1323   } else {
1324     duration = GST_BUFFER_TIMESTAMP (buf) - GST_BUFFER_TIMESTAMP (last_buf);
1325   }
1326
1327   gst_buffer_replace (&pad->last_buf, buf);
1328
1329   last_dts = gst_util_uint64_scale (pad->last_dts,
1330       atom_trak_get_timescale (pad->trak), GST_SECOND);
1331
1332   /* raw audio has many samples per buffer (= chunk) */
1333   if (pad->sample_size) {
1334     sample_size = pad->sample_size;
1335     if (GST_BUFFER_SIZE (last_buf) % sample_size != 0)
1336       goto fragmented_sample;
1337     /* note: qt raw audio storage warps it implicitly into a timewise
1338      * perfect stream, discarding buffer times */
1339     nsamples = GST_BUFFER_SIZE (last_buf) / sample_size;
1340     duration = GST_BUFFER_DURATION (last_buf) / nsamples;
1341     /* timescale = samplerate */
1342     scaled_duration = 1;
1343     pad->last_dts += duration * nsamples;
1344   } else {
1345     nsamples = 1;
1346     sample_size = GST_BUFFER_SIZE (last_buf);
1347     if (pad->have_dts) {
1348       gint64 scaled_dts;
1349       pad->last_dts = GST_BUFFER_OFFSET_END (last_buf);
1350       if ((gint64) (pad->last_dts) < 0) {
1351         scaled_dts = -gst_util_uint64_scale (-pad->last_dts,
1352             atom_trak_get_timescale (pad->trak), GST_SECOND);
1353       } else {
1354         scaled_dts = gst_util_uint64_scale (pad->last_dts,
1355             atom_trak_get_timescale (pad->trak), GST_SECOND);
1356       }
1357       scaled_duration = scaled_dts - last_dts;
1358       last_dts = scaled_dts;
1359     } else {
1360       /* first convert intended timestamp (in GstClockTime resolution) to
1361        * trak timescale, then derive delta;
1362        * this ensures sums of (scale)delta add up to converted timestamp,
1363        * which only deviates at most 1/scale from timestamp itself */
1364       scaled_duration = gst_util_uint64_scale (pad->last_dts + duration,
1365           atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts;
1366       pad->last_dts += duration;
1367     }
1368   }
1369   chunk_offset = qtmux->mdat_size;
1370
1371   GST_LOG_OBJECT (qtmux,
1372       "Pad (%s) dts updated to %" GST_TIME_FORMAT,
1373       GST_PAD_NAME (pad->collect.pad), GST_TIME_ARGS (pad->last_dts));
1374   GST_LOG_OBJECT (qtmux,
1375       "Adding %d samples to track, duration: %" G_GUINT64_FORMAT
1376       " size: %" G_GUINT32_FORMAT " chunk offset: %" G_GUINT64_FORMAT,
1377       nsamples, scaled_duration, sample_size, chunk_offset);
1378
1379   /* might be a sync sample */
1380   if (pad->sync &&
1381       !GST_BUFFER_FLAG_IS_SET (last_buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
1382     GST_LOG_OBJECT (qtmux, "Adding new sync sample entry for track of pad %s",
1383         GST_PAD_NAME (pad->collect.pad));
1384     sync = TRUE;
1385   }
1386
1387   /* optionally calculate ctts entry values
1388    * (if composition-time expected different from decoding-time) */
1389   /* really not recommended:
1390    * - decoder typically takes care of dts/pts issues
1391    * - in case of out-of-order, dts may only be determined as above
1392    *   (e.g. sum of duration), which may be totally different from
1393    *   buffer timestamps in case of multiple segment, non-perfect streams
1394    *  (and just perhaps maybe with some luck segment_to_running_time
1395    *   or segment_to_media_time might get near to it) */
1396   if ((pad->have_dts || qtmux->guess_pts) && pad->is_out_of_order) {
1397     guint64 pts;
1398
1399     pts = gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (last_buf),
1400         atom_trak_get_timescale (pad->trak), GST_SECOND);
1401     pts_offset = (gint64) (pts - last_dts);
1402     do_pts = TRUE;
1403     GST_LOG_OBJECT (qtmux, "Adding ctts entry for pad %s: %" G_GINT64_FORMAT,
1404         GST_PAD_NAME (pad->collect.pad), pts_offset);
1405   }
1406
1407   /*
1408    * Each buffer starts a new chunk, so we can assume the buffer
1409    * duration is the chunk duration
1410    */
1411   if (GST_CLOCK_TIME_IS_VALID (duration) && (duration > qtmux->longest_chunk ||
1412           !GST_CLOCK_TIME_IS_VALID (qtmux->longest_chunk))) {
1413     GST_DEBUG_OBJECT (qtmux, "New longest chunk found: %" GST_TIME_FORMAT
1414         ", pad %s", GST_TIME_ARGS (duration), GST_PAD_NAME (pad->collect.pad));
1415     qtmux->longest_chunk = duration;
1416   }
1417
1418   /* if this is the first buffer, store the timestamp */
1419   if (G_UNLIKELY (pad->first_ts == GST_CLOCK_TIME_NONE) && last_buf) {
1420     if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (last_buf))) {
1421       pad->first_ts = GST_BUFFER_TIMESTAMP (last_buf);
1422     } else {
1423       GST_DEBUG_OBJECT (qtmux, "First buffer for pad %s has no timestamp, "
1424           "using 0 as first timestamp", GST_PAD_NAME (pad->collect.pad));
1425       pad->first_ts = 0;
1426     }
1427     GST_DEBUG_OBJECT (qtmux, "Stored first timestamp for pad %s %"
1428         GST_TIME_FORMAT, GST_PAD_NAME (pad->collect.pad),
1429         GST_TIME_ARGS (pad->first_ts));
1430   }
1431
1432   /* now we go and register this buffer/sample all over */
1433   /* note that a new chunk is started each time (not fancy but works) */
1434   atom_trak_add_samples (pad->trak, nsamples, scaled_duration, sample_size,
1435       chunk_offset, sync, do_pts, pts_offset);
1436
1437   if (buf)
1438     gst_buffer_unref (buf);
1439
1440   return gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE);
1441
1442   /* ERRORS */
1443 bail:
1444   {
1445     if (buf)
1446       gst_buffer_unref (buf);
1447     gst_buffer_unref (last_buf);
1448     return GST_FLOW_ERROR;
1449   }
1450 no_time:
1451   {
1452     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1453         ("Received buffer without timestamp/duration."));
1454     goto bail;
1455   }
1456 fragmented_sample:
1457   {
1458     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1459         ("Audio buffer contains fragmented sample."));
1460     goto bail;
1461   }
1462 not_negotiated:
1463   {
1464     GST_ELEMENT_ERROR (qtmux, CORE, NEGOTIATION, (NULL),
1465         ("format wasn't negotiated before buffer flow on pad %s",
1466             GST_PAD_NAME (pad->collect.pad)));
1467     if (buf)
1468       gst_buffer_unref (buf);
1469     return GST_FLOW_NOT_NEGOTIATED;
1470   }
1471 }
1472
1473 static GstFlowReturn
1474 gst_qt_mux_collected (GstCollectPads * pads, gpointer user_data)
1475 {
1476   GstFlowReturn ret = GST_FLOW_OK;
1477   GstQTMux *qtmux = GST_QT_MUX_CAST (user_data);
1478   GSList *walk;
1479   GstQTPad *best_pad = NULL;
1480   GstClockTime time, best_time = GST_CLOCK_TIME_NONE;
1481   GstBuffer *buf;
1482
1483   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_STARTED)) {
1484     if ((ret = gst_qt_mux_start_file (qtmux)) != GST_FLOW_OK)
1485       return ret;
1486     else
1487       qtmux->state = GST_QT_MUX_STATE_DATA;
1488   }
1489
1490   if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS))
1491     return GST_FLOW_UNEXPECTED;
1492
1493   /* select the best buffer */
1494   walk = qtmux->collect->data;
1495   while (walk) {
1496     GstQTPad *pad;
1497     GstCollectData *data;
1498
1499     data = (GstCollectData *) walk->data;
1500     pad = (GstQTPad *) data;
1501
1502     walk = g_slist_next (walk);
1503
1504     buf = gst_collect_pads_peek (pads, data);
1505     if (buf == NULL) {
1506       GST_LOG_OBJECT (qtmux, "Pad %s has no buffers",
1507           GST_PAD_NAME (pad->collect.pad));
1508       continue;
1509     }
1510     time = GST_BUFFER_TIMESTAMP (buf);
1511     gst_buffer_unref (buf);
1512
1513     if (best_pad == NULL || !GST_CLOCK_TIME_IS_VALID (time) ||
1514         (GST_CLOCK_TIME_IS_VALID (best_time) && time < best_time)) {
1515       best_pad = pad;
1516       best_time = time;
1517     }
1518   }
1519
1520   if (best_pad != NULL) {
1521     GST_LOG_OBJECT (qtmux, "selected pad %s with time %" GST_TIME_FORMAT,
1522         GST_PAD_NAME (best_pad->collect.pad), GST_TIME_ARGS (best_time));
1523     buf = gst_collect_pads_pop (pads, &best_pad->collect);
1524     ret = gst_qt_mux_add_buffer (qtmux, best_pad, buf);
1525   } else {
1526     ret = gst_qt_mux_stop_file (qtmux);
1527     if (ret == GST_FLOW_OK) {
1528       gst_pad_push_event (qtmux->srcpad, gst_event_new_eos ());
1529       ret = GST_FLOW_UNEXPECTED;
1530     }
1531     qtmux->state = GST_QT_MUX_STATE_EOS;
1532   }
1533
1534   return ret;
1535 }
1536
1537 static gboolean
1538 gst_qt_mux_audio_sink_set_caps (GstPad * pad, GstCaps * caps)
1539 {
1540   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1541   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1542   GstQTPad *qtpad = NULL;
1543   GstStructure *structure;
1544   const gchar *mimetype;
1545   gint rate, channels;
1546   const GValue *value = NULL;
1547   const GstBuffer *codec_data = NULL;
1548   GstQTMuxFormat format;
1549   AudioSampleEntry entry = { 0, };
1550   AtomInfo *ext_atom = NULL;
1551   gint constant_size = 0;
1552
1553   /* find stream data */
1554   qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
1555   g_assert (qtpad);
1556
1557   /* does not go well to renegotiate stream mid-way */
1558   if (qtpad->fourcc)
1559     goto refuse_renegotiation;
1560
1561   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
1562       GST_DEBUG_PAD_NAME (pad), caps);
1563
1564   format = qtmux_klass->format;
1565   structure = gst_caps_get_structure (caps, 0);
1566   mimetype = gst_structure_get_name (structure);
1567
1568   /* common info */
1569   if (!gst_structure_get_int (structure, "channels", &channels) ||
1570       !gst_structure_get_int (structure, "rate", &rate)) {
1571     goto refuse_caps;
1572   }
1573
1574   /* optional */
1575   value = gst_structure_get_value (structure, "codec_data");
1576   if (value != NULL)
1577     codec_data = gst_value_get_buffer (value);
1578
1579   qtpad->is_out_of_order = FALSE;
1580   qtpad->have_dts = FALSE;
1581
1582   /* set common properties */
1583   entry.sample_rate = rate;
1584   entry.channels = channels;
1585   /* default */
1586   entry.sample_size = 16;
1587   /* this is the typical compressed case */
1588   if (format == GST_QT_MUX_FORMAT_QT) {
1589     entry.version = 1;
1590     entry.compression_id = -2;
1591   }
1592
1593   /* now map onto a fourcc, and some extra properties */
1594   if (strcmp (mimetype, "audio/mpeg") == 0) {
1595     gint mpegversion = 0;
1596     gint layer = -1;
1597
1598     gst_structure_get_int (structure, "mpegversion", &mpegversion);
1599     switch (mpegversion) {
1600       case 1:
1601         gst_structure_get_int (structure, "layer", &layer);
1602         switch (layer) {
1603           case 3:
1604             /* mp3 */
1605             /* note: QuickTime player does not like mp3 either way in iso/mp4 */
1606             if (format == GST_QT_MUX_FORMAT_QT)
1607               entry.fourcc = FOURCC__mp3;
1608             else {
1609               entry.fourcc = FOURCC_mp4a;
1610               ext_atom =
1611                   build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG1_P3,
1612                   ESDS_STREAM_TYPE_AUDIO, codec_data);
1613             }
1614             entry.samples_per_packet = 1152;
1615             entry.bytes_per_sample = 2;
1616             break;
1617         }
1618         break;
1619       case 4:
1620         /* AAC */
1621         entry.fourcc = FOURCC_mp4a;
1622         if (!codec_data || GST_BUFFER_SIZE (codec_data) < 2)
1623           GST_WARNING_OBJECT (qtmux, "no (valid) codec_data for AAC audio");
1624         else {
1625           guint8 profile = GST_READ_UINT8 (GST_BUFFER_DATA (codec_data));
1626
1627           /* warn if not Low Complexity profile */
1628           profile >>= 3;
1629           if (profile != 2)
1630             GST_WARNING_OBJECT (qtmux,
1631                 "non-LC AAC may not run well on (Apple) QuickTime/iTunes");
1632         }
1633         if (format == GST_QT_MUX_FORMAT_QT)
1634           ext_atom = build_mov_aac_extension (qtpad->trak, codec_data);
1635         else
1636           ext_atom =
1637               build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P3,
1638               ESDS_STREAM_TYPE_AUDIO, codec_data);
1639         break;
1640       default:
1641         break;
1642     }
1643   } else if (strcmp (mimetype, "audio/AMR") == 0) {
1644     entry.fourcc = FOURCC_samr;
1645     entry.sample_size = 16;
1646     entry.samples_per_packet = 160;
1647     entry.bytes_per_sample = 2;
1648     ext_atom = build_amr_extension ();
1649   } else if (strcmp (mimetype, "audio/AMR-WB") == 0) {
1650     entry.fourcc = FOURCC_sawb;
1651     entry.sample_size = 16;
1652     entry.samples_per_packet = 320;
1653     entry.bytes_per_sample = 2;
1654     ext_atom = build_amr_extension ();
1655   } else if (strcmp (mimetype, "audio/x-raw-int") == 0) {
1656     gint width;
1657     gint depth;
1658     gint endianness;
1659     gboolean sign;
1660
1661     if (!gst_structure_get_int (structure, "width", &width) ||
1662         !gst_structure_get_int (structure, "depth", &depth) ||
1663         !gst_structure_get_boolean (structure, "signed", &sign)) {
1664       GST_DEBUG_OBJECT (qtmux, "broken caps, width/depth/signed field missing");
1665       goto refuse_caps;
1666     }
1667
1668     if (depth <= 8) {
1669       endianness = G_BYTE_ORDER;
1670     } else if (!gst_structure_get_boolean (structure,
1671             "endianness", &endianness)) {
1672       GST_DEBUG_OBJECT (qtmux, "broken caps, endianness field missing");
1673       goto refuse_caps;
1674     }
1675
1676     /* spec has no place for a distinction in these */
1677     if (width != depth) {
1678       GST_DEBUG_OBJECT (qtmux, "width must be same as depth!");
1679       goto refuse_caps;
1680     }
1681
1682     if (sign) {
1683       if (endianness == G_LITTLE_ENDIAN)
1684         entry.fourcc = FOURCC_sowt;
1685       else if (endianness == G_BIG_ENDIAN)
1686         entry.fourcc = FOURCC_twos;
1687       /* maximum backward compatibility; only new version for > 16 bit */
1688       if (depth <= 16)
1689         entry.version = 0;
1690       /* not compressed in any case */
1691       entry.compression_id = 0;
1692       /* QT spec says: max at 16 bit even if sample size were actually larger,
1693        * however, most players (e.g. QuickTime!) seem to disagree, so ... */
1694       entry.sample_size = depth;
1695       entry.bytes_per_sample = depth / 8;
1696       entry.samples_per_packet = 1;
1697       entry.bytes_per_packet = depth / 8;
1698       entry.bytes_per_frame = entry.bytes_per_packet * channels;
1699     } else {
1700       if (width == 8 && depth == 8) {
1701         /* fall back to old 8-bit version */
1702         entry.fourcc = FOURCC_raw_;
1703         entry.version = 0;
1704         entry.compression_id = 0;
1705         entry.sample_size = 8;
1706       } else {
1707         GST_DEBUG_OBJECT (qtmux, "non 8-bit PCM must be signed");
1708         goto refuse_caps;
1709       }
1710     }
1711     constant_size = (depth / 8) * channels;
1712   } else if (strcmp (mimetype, "audio/x-alaw") == 0) {
1713     entry.fourcc = FOURCC_alaw;
1714     entry.samples_per_packet = 1023;
1715     entry.bytes_per_sample = 2;
1716   } else if (strcmp (mimetype, "audio/x-mulaw") == 0) {
1717     entry.fourcc = FOURCC_ulaw;
1718     entry.samples_per_packet = 1023;
1719     entry.bytes_per_sample = 2;
1720   }
1721
1722   if (!entry.fourcc)
1723     goto refuse_caps;
1724
1725   /* ok, set the pad info accordingly */
1726   qtpad->fourcc = entry.fourcc;
1727   qtpad->sample_size = constant_size;
1728   atom_trak_set_audio_type (qtpad->trak, qtmux->context, &entry,
1729       entry.sample_rate, ext_atom, constant_size);
1730
1731   gst_object_unref (qtmux);
1732   return TRUE;
1733
1734   /* ERRORS */
1735 refuse_caps:
1736   {
1737     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
1738         GST_PAD_NAME (pad), caps);
1739     gst_object_unref (qtmux);
1740     return FALSE;
1741   }
1742 refuse_renegotiation:
1743   {
1744     GST_WARNING_OBJECT (qtmux,
1745         "pad %s refused renegotiation to %" GST_PTR_FORMAT,
1746         GST_PAD_NAME (pad), caps);
1747     gst_object_unref (qtmux);
1748     return FALSE;
1749   }
1750 }
1751
1752 /* scale rate up or down by factor of 10 to fit into [1000,10000] interval */
1753 static guint32
1754 adjust_rate (guint64 rate)
1755 {
1756   while (rate >= 10000)
1757     rate /= 10;
1758
1759   while (rate < 1000)
1760     rate *= 10;
1761
1762   return (guint32) rate;
1763 }
1764
1765 static gboolean
1766 gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps)
1767 {
1768   GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1769   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1770   GstQTPad *qtpad = NULL;
1771   GstStructure *structure;
1772   const gchar *mimetype;
1773   gint width, height, depth = -1;
1774   gint framerate_num, framerate_den;
1775   guint32 rate;
1776   const GValue *value = NULL;
1777   const GstBuffer *codec_data = NULL;
1778   VisualSampleEntry entry = { 0, };
1779   GstQTMuxFormat format;
1780   AtomInfo *ext_atom = NULL;
1781   GList *ext_atom_list = NULL;
1782   gboolean sync = FALSE;
1783   int par_num, par_den;
1784
1785   /* find stream data */
1786   qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
1787   g_assert (qtpad);
1788
1789   /* does not go well to renegotiate stream mid-way */
1790   if (qtpad->fourcc)
1791     goto refuse_renegotiation;
1792
1793   GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
1794       GST_DEBUG_PAD_NAME (pad), caps);
1795
1796   format = qtmux_klass->format;
1797   structure = gst_caps_get_structure (caps, 0);
1798   mimetype = gst_structure_get_name (structure);
1799
1800   /* required parts */
1801   if (!gst_structure_get_int (structure, "width", &width) ||
1802       !gst_structure_get_int (structure, "height", &height))
1803     goto refuse_caps;
1804
1805   /* optional */
1806   depth = -1;
1807   /* works as a default timebase */
1808   framerate_num = 10000;
1809   framerate_den = 1;
1810   gst_structure_get_fraction (structure, "framerate", &framerate_num,
1811       &framerate_den);
1812   gst_structure_get_int (structure, "depth", &depth);
1813   value = gst_structure_get_value (structure, "codec_data");
1814   if (value != NULL)
1815     codec_data = gst_value_get_buffer (value);
1816
1817   par_num = 1;
1818   par_den = 1;
1819   gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_num,
1820       &par_den);
1821
1822   qtpad->is_out_of_order = FALSE;
1823
1824   /* bring frame numerator into a range that ensures both reasonable resolution
1825    * as well as a fair duration */
1826   rate = adjust_rate (framerate_num);
1827   GST_DEBUG_OBJECT (qtmux, "Rate of video track selected: %" G_GUINT32_FORMAT,
1828       rate);
1829
1830   /* set common properties */
1831   entry.width = width;
1832   entry.height = height;
1833   entry.par_n = par_num;
1834   entry.par_d = par_den;
1835   /* should be OK according to qt and iso spec, override if really needed */
1836   entry.color_table_id = -1;
1837   entry.frame_count = 1;
1838   entry.depth = 24;
1839
1840   /* sync entries by default */
1841   sync = TRUE;
1842
1843   /* now map onto a fourcc, and some extra properties */
1844   if (strcmp (mimetype, "video/x-raw-rgb") == 0) {
1845     gint bpp;
1846
1847     entry.fourcc = FOURCC_raw_;
1848     gst_structure_get_int (structure, "bpp", &bpp);
1849     entry.depth = bpp;
1850     sync = FALSE;
1851   } else if (strcmp (mimetype, "video/x-raw-yuv") == 0) {
1852     guint32 format = 0;
1853
1854     sync = FALSE;
1855     gst_structure_get_fourcc (structure, "format", &format);
1856     switch (format) {
1857       case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
1858         if (depth == -1)
1859           depth = 24;
1860         entry.fourcc = FOURCC_2vuy;
1861         entry.depth = depth;
1862         break;
1863     }
1864   } else if (strcmp (mimetype, "video/x-h263") == 0) {
1865     ext_atom = NULL;
1866     if (format == GST_QT_MUX_FORMAT_QT)
1867       entry.fourcc = FOURCC_h263;
1868     else
1869       entry.fourcc = FOURCC_s263;
1870     ext_atom = build_h263_extension ();
1871     if (ext_atom != NULL)
1872       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
1873   } else if (strcmp (mimetype, "video/x-divx") == 0 ||
1874       strcmp (mimetype, "video/mpeg") == 0) {
1875     gint version = 0;
1876
1877     if (strcmp (mimetype, "video/x-divx") == 0) {
1878       gst_structure_get_int (structure, "divxversion", &version);
1879       version = version == 5 ? 1 : 0;
1880     } else {
1881       gst_structure_get_int (structure, "mpegversion", &version);
1882       version = version == 4 ? 1 : 0;
1883     }
1884     if (version) {
1885       entry.fourcc = FOURCC_mp4v;
1886       ext_atom =
1887           build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P2,
1888           ESDS_STREAM_TYPE_VISUAL, codec_data);
1889       if (ext_atom != NULL)
1890         ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
1891       if (!codec_data)
1892         GST_WARNING_OBJECT (qtmux, "no codec_data for MPEG4 video; "
1893             "output might not play in Apple QuickTime (try global-headers?)");
1894     }
1895   } else if (strcmp (mimetype, "video/x-h264") == 0) {
1896     entry.fourcc = FOURCC_avc1;
1897     qtpad->is_out_of_order = TRUE;
1898     if (!codec_data)
1899       GST_WARNING_OBJECT (qtmux, "no codec_data in h264 caps");
1900     ext_atom = build_codec_data_extension (FOURCC_avcC, codec_data);
1901     if (ext_atom != NULL)
1902       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
1903   } else if (strcmp (mimetype, "video/x-svq") == 0) {
1904     gint version = 0;
1905     const GstBuffer *seqh = NULL;
1906     const GValue *seqh_value;
1907     gdouble gamma = 0;
1908
1909     gst_structure_get_int (structure, "svqversion", &version);
1910     if (version == 3) {
1911       entry.fourcc = FOURCC_SVQ3;
1912       entry.version = 3;
1913       entry.depth = 32;
1914       qtpad->is_out_of_order = TRUE;
1915
1916       seqh_value = gst_structure_get_value (structure, "seqh");
1917       if (seqh_value) {
1918         seqh = gst_value_get_buffer (seqh_value);
1919         ext_atom = build_SMI_atom (seqh);
1920         if (ext_atom)
1921           ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
1922       }
1923
1924       /* we need to add the gamma anyway because quicktime might crash
1925        * when it doesn't find it */
1926       if (!gst_structure_get_double (structure, "applied-gamma", &gamma)) {
1927         /* it seems that using 0 here makes it ignored */
1928         gamma = 0.0;
1929       }
1930       ext_atom = build_gama_atom (gamma);
1931       if (ext_atom)
1932         ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
1933     } else {
1934       GST_WARNING_OBJECT (qtmux, "SVQ version %d not supported. Please file "
1935           "a bug at http://bugzilla.gnome.org", version);
1936     }
1937   } else if (strcmp (mimetype, "video/x-dv") == 0) {
1938     gint version = 0;
1939     gboolean pal = TRUE;
1940
1941     sync = FALSE;
1942     if (framerate_num != 25 || framerate_den != 1)
1943       pal = FALSE;
1944     gst_structure_get_int (structure, "dvversion", &version);
1945     /* fall back to typical one */
1946     if (!version)
1947       version = 25;
1948     switch (version) {
1949       case 25:
1950         if (pal)
1951           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', 'p');
1952         else
1953           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', ' ');
1954         break;
1955       case 50:
1956         if (pal)
1957           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'p');
1958         else
1959           entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'n');
1960         break;
1961       default:
1962         GST_WARNING_OBJECT (qtmux, "unrecognized dv version");
1963         break;
1964     }
1965   } else if (strcmp (mimetype, "image/jpeg") == 0) {
1966     entry.fourcc = FOURCC_jpeg;
1967     sync = FALSE;
1968   } else if (strcmp (mimetype, "image/x-j2c") == 0) {
1969     guint32 fourcc;
1970
1971     ext_atom = NULL;
1972     entry.fourcc = FOURCC_mjp2;
1973     sync = FALSE;
1974     if (!gst_structure_get_fourcc (structure, "fourcc", &fourcc) ||
1975         !(ext_atom =
1976             build_jp2h_extension (qtpad->trak, width, height, fourcc))) {
1977       GST_DEBUG_OBJECT (qtmux, "missing or invalid fourcc in jp2 caps");
1978       goto refuse_caps;
1979     }
1980     if (ext_atom)
1981       ext_atom_list = g_list_prepend (ext_atom_list, ext_atom);
1982   } else if (strcmp (mimetype, "video/x-qt-part") == 0) {
1983     guint32 fourcc;
1984
1985     gst_structure_get_fourcc (structure, "format", &fourcc);
1986     entry.fourcc = fourcc;
1987     qtpad->is_out_of_order = TRUE;
1988     qtpad->have_dts = TRUE;
1989   } else if (strcmp (mimetype, "video/x-mp4-part") == 0) {
1990     guint32 fourcc;
1991
1992     gst_structure_get_fourcc (structure, "format", &fourcc);
1993     entry.fourcc = fourcc;
1994     qtpad->is_out_of_order = TRUE;
1995     qtpad->have_dts = TRUE;
1996   }
1997
1998   if (!entry.fourcc)
1999     goto refuse_caps;
2000
2001   /* ok, set the pad info accordingly */
2002   qtpad->fourcc = entry.fourcc;
2003   qtpad->sync = sync;
2004   atom_trak_set_video_type (qtpad->trak, qtmux->context, &entry, rate,
2005       ext_atom_list);
2006
2007   gst_object_unref (qtmux);
2008   return TRUE;
2009
2010   /* ERRORS */
2011 refuse_caps:
2012   {
2013     GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
2014         GST_PAD_NAME (pad), caps);
2015     gst_object_unref (qtmux);
2016     return FALSE;
2017   }
2018 refuse_renegotiation:
2019   {
2020     GST_WARNING_OBJECT (qtmux,
2021         "pad %s refused renegotiation to %" GST_PTR_FORMAT " from %"
2022         GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, GST_PAD_CAPS (pad));
2023     gst_object_unref (qtmux);
2024     return FALSE;
2025   }
2026 }
2027
2028 static gboolean
2029 gst_qt_mux_sink_event (GstPad * pad, GstEvent * event)
2030 {
2031   gboolean ret;
2032   GstQTMux *qtmux;
2033
2034   qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
2035   switch (GST_EVENT_TYPE (event)) {
2036     case GST_EVENT_TAG:{
2037       GstTagList *list;
2038       GstTagSetter *setter = GST_TAG_SETTER (qtmux);
2039       const GstTagMergeMode mode = gst_tag_setter_get_tag_merge_mode (setter);
2040
2041       GST_DEBUG_OBJECT (qtmux, "received tag event");
2042       gst_event_parse_tag (event, &list);
2043       gst_tag_setter_merge_tags (setter, list, mode);
2044       break;
2045     }
2046     default:
2047       break;
2048   }
2049
2050   ret = qtmux->collect_event (pad, event);
2051   gst_object_unref (qtmux);
2052
2053   return ret;
2054 }
2055
2056 static void
2057 gst_qt_mux_release_pad (GstElement * element, GstPad * pad)
2058 {
2059   GstQTMux *mux = GST_QT_MUX_CAST (element);
2060
2061   /* let GstCollectPads complain if it is some unknown pad */
2062   if (gst_collect_pads_remove_pad (mux->collect, pad))
2063     gst_element_remove_pad (element, pad);
2064 }
2065
2066 static GstPad *
2067 gst_qt_mux_request_new_pad (GstElement * element,
2068     GstPadTemplate * templ, const gchar * name)
2069 {
2070   GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
2071   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
2072   GstQTPad *collect_pad;
2073   GstPad *newpad;
2074   gboolean audio;
2075
2076   GST_DEBUG_OBJECT (qtmux, "Requested pad: %s", GST_STR_NULL (name));
2077
2078   if (qtmux->state != GST_QT_MUX_STATE_NONE) {
2079     GST_WARNING_OBJECT (qtmux, "Not providing request pad after stream start.");
2080     return NULL;
2081   }
2082
2083   if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
2084     audio = TRUE;
2085   } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
2086     audio = FALSE;
2087   } else {
2088     GST_WARNING_OBJECT (qtmux, "This is not our template!");
2089     return NULL;
2090   }
2091
2092   /* add pad to collections */
2093   newpad = gst_pad_new_from_template (templ, name);
2094   collect_pad = (GstQTPad *)
2095       gst_collect_pads_add_pad_full (qtmux->collect, newpad, sizeof (GstQTPad),
2096       (GstCollectDataDestroyNotify) (gst_qt_mux_pad_reset));
2097   /* set up pad */
2098   gst_qt_mux_pad_reset (collect_pad);
2099   collect_pad->trak = atom_trak_new (qtmux->context);
2100   atom_moov_add_trak (qtmux->moov, collect_pad->trak);
2101
2102   /* set up pad functions */
2103   if (audio)
2104     gst_pad_set_setcaps_function (newpad,
2105         GST_DEBUG_FUNCPTR (gst_qt_mux_audio_sink_set_caps));
2106   else
2107     gst_pad_set_setcaps_function (newpad,
2108         GST_DEBUG_FUNCPTR (gst_qt_mux_video_sink_set_caps));
2109
2110   /* FIXME: hacked way to override/extend the event function of
2111    * GstCollectPads; because it sets its own event function giving the
2112    * element no access to events.
2113    */
2114   qtmux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
2115   gst_pad_set_event_function (newpad,
2116       GST_DEBUG_FUNCPTR (gst_qt_mux_sink_event));
2117
2118   gst_pad_set_active (newpad, TRUE);
2119   gst_element_add_pad (element, newpad);
2120
2121   return newpad;
2122 }
2123
2124 static void
2125 gst_qt_mux_get_property (GObject * object,
2126     guint prop_id, GValue * value, GParamSpec * pspec)
2127 {
2128   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
2129
2130   GST_OBJECT_LOCK (qtmux);
2131   switch (prop_id) {
2132     case PROP_LARGE_FILE:
2133       g_value_set_boolean (value, qtmux->large_file);
2134       break;
2135     case PROP_MOVIE_TIMESCALE:
2136       g_value_set_uint (value, qtmux->timescale);
2137       break;
2138     case PROP_DO_CTTS:
2139       g_value_set_boolean (value, qtmux->guess_pts);
2140       break;
2141     case PROP_FAST_START:
2142       g_value_set_boolean (value, qtmux->fast_start);
2143       break;
2144     case PROP_FAST_START_TEMP_FILE:
2145       g_value_set_string (value, qtmux->fast_start_file_path);
2146       break;
2147     default:
2148       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2149       break;
2150   }
2151   GST_OBJECT_UNLOCK (qtmux);
2152 }
2153
2154 static void
2155 gst_qt_mux_generate_fast_start_file_path (GstQTMux * qtmux)
2156 {
2157   gchar *tmp;
2158
2159   if (qtmux->fast_start_file_path) {
2160     g_free (qtmux->fast_start_file_path);
2161     qtmux->fast_start_file_path = NULL;
2162   }
2163
2164   tmp = g_strdup_printf ("%s%d", "qtmux", g_random_int ());
2165   qtmux->fast_start_file_path = g_build_filename (g_get_tmp_dir (), tmp, NULL);
2166   g_free (tmp);
2167 }
2168
2169 static void
2170 gst_qt_mux_set_property (GObject * object,
2171     guint prop_id, const GValue * value, GParamSpec * pspec)
2172 {
2173   GstQTMux *qtmux = GST_QT_MUX_CAST (object);
2174
2175   GST_OBJECT_LOCK (qtmux);
2176   switch (prop_id) {
2177     case PROP_LARGE_FILE:
2178       qtmux->large_file = g_value_get_boolean (value);
2179       break;
2180     case PROP_MOVIE_TIMESCALE:
2181       qtmux->timescale = g_value_get_uint (value);
2182       break;
2183     case PROP_DO_CTTS:
2184       qtmux->guess_pts = g_value_get_boolean (value);
2185       break;
2186     case PROP_FAST_START:
2187       qtmux->fast_start = g_value_get_boolean (value);
2188       break;
2189     case PROP_FAST_START_TEMP_FILE:
2190       if (qtmux->fast_start_file_path) {
2191         g_free (qtmux->fast_start_file_path);
2192       }
2193       qtmux->fast_start_file_path = g_value_dup_string (value);
2194       /* NULL means to generate a random one */
2195       if (!qtmux->fast_start_file_path) {
2196         gst_qt_mux_generate_fast_start_file_path (qtmux);
2197       }
2198       break;
2199     default:
2200       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2201       break;
2202   }
2203   GST_OBJECT_UNLOCK (qtmux);
2204 }
2205
2206 static GstStateChangeReturn
2207 gst_qt_mux_change_state (GstElement * element, GstStateChange transition)
2208 {
2209   GstStateChangeReturn ret;
2210   GstQTMux *qtmux = GST_QT_MUX_CAST (element);
2211
2212   switch (transition) {
2213     case GST_STATE_CHANGE_NULL_TO_READY:
2214       break;
2215     case GST_STATE_CHANGE_READY_TO_PAUSED:
2216       gst_collect_pads_start (qtmux->collect);
2217       qtmux->state = GST_QT_MUX_STATE_STARTED;
2218       break;
2219     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
2220       break;
2221     case GST_STATE_CHANGE_PAUSED_TO_READY:
2222       gst_collect_pads_stop (qtmux->collect);
2223       break;
2224     default:
2225       break;
2226   }
2227
2228   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2229
2230   switch (transition) {
2231     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
2232       break;
2233     case GST_STATE_CHANGE_PAUSED_TO_READY:
2234       gst_qt_mux_reset (qtmux, TRUE);
2235       break;
2236     case GST_STATE_CHANGE_READY_TO_NULL:
2237       break;
2238     default:
2239       break;
2240   }
2241
2242   return ret;
2243 }
2244
2245
2246 gboolean
2247 gst_qt_mux_register (GstPlugin * plugin)
2248 {
2249   GTypeInfo typeinfo = {
2250     sizeof (GstQTMuxClass),
2251     (GBaseInitFunc) gst_qt_mux_base_init,
2252     NULL,
2253     (GClassInitFunc) gst_qt_mux_class_init,
2254     NULL,
2255     NULL,
2256     sizeof (GstQTMux),
2257     0,
2258     (GInstanceInitFunc) gst_qt_mux_init,
2259   };
2260   static const GInterfaceInfo tag_setter_info = {
2261     NULL, NULL, NULL
2262   };
2263   GType type;
2264   GstQTMuxFormat format;
2265   GstQTMuxClassParams *params;
2266   guint i = 0;
2267
2268   GST_LOG ("Registering muxers");
2269
2270   while (TRUE) {
2271     GstQTMuxFormatProp *prop;
2272
2273     prop = &gst_qt_mux_format_list[i];
2274     format = prop->format;
2275     if (format == GST_QT_MUX_FORMAT_NONE)
2276       break;
2277
2278     /* create a cache for these properties */
2279     params = g_new0 (GstQTMuxClassParams, 1);
2280     params->prop = prop;
2281     params->src_caps = gst_static_caps_get (&prop->src_caps);
2282     params->video_sink_caps = gst_static_caps_get (&prop->video_sink_caps);
2283     params->audio_sink_caps = gst_static_caps_get (&prop->audio_sink_caps);
2284
2285     /* create the type now */
2286     type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo,
2287         0);
2288     g_type_set_qdata (type, GST_QT_MUX_PARAMS_QDATA, (gpointer) params);
2289     g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
2290
2291     if (!gst_element_register (plugin, prop->name, GST_RANK_PRIMARY, type))
2292       return FALSE;
2293
2294     i++;
2295   }
2296
2297   GST_LOG ("Finished registering muxers");
2298
2299   return TRUE;
2300 }
2301
2302 gboolean
2303 gst_qt_mux_plugin_init (GstPlugin * plugin)
2304 {
2305   GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
2306
2307   return gst_qt_mux_register (plugin);
2308 }
2309
2310 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
2311     GST_VERSION_MINOR,
2312     "qtmux",
2313     "Quicktime Muxer plugin",
2314     gst_qt_mux_plugin_init, VERSION, "LGPL", "gsoc2008 package",
2315     "embedded.ufcg.edu.br")