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