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>
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.
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.
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.
21 * Unless otherwise indicated, Source Code is licensed under MIT license.
22 * See further explanation attached in License Statement (distributed in the file
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:
32 * The above copyright notice and this permission notice shall be included in all
33 * copies or substantial portions of the Software.
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
47 * @short_description: Muxer for quicktime(.mov) files
51 * This element merges streams (audio and video) into qt(.mov) files.
53 * <title>Example pipelines</title>
56 * gst-launch v4l2src num-buffers=500 ! video/x-raw-yuv,width=320,height=240 ! ffmpegcolorspace ! qtmux ! filesink location=video.mov
58 * Records a video stream captured from a v4l2 device and muxes it into a qt file.
62 * Last reviewed on 2008-08-27
73 #include <glib/gstdio.h>
76 #include <gst/base/gstcollectpads.h>
78 #include <sys/types.h>
80 #include <io.h> /* lseek, open, close, read */
82 #define lseek _lseeki64
93 GST_DEBUG_CATEGORY_STATIC (gst_qt_mux_debug);
94 #define GST_CAT_DEFAULT gst_qt_mux_debug
96 /* QTMux signals and args */
107 PROP_MOVIE_TIMESCALE,
111 PROP_FAST_START_TEMP_FILE
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
121 static void gst_qt_mux_finalize (GObject * object);
123 static GstStateChangeReturn gst_qt_mux_change_state (GstElement * element,
124 GstStateChange transition);
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);
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);
138 static gboolean gst_qt_mux_sink_event (GstPad * pad, GstEvent * event);
140 static GstFlowReturn gst_qt_mux_collected (GstCollectPads * pads,
142 static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
145 static GstElementClass *parent_class = NULL;
148 gst_qt_mux_base_init (gpointer g_class)
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;
157 (GstQTMuxClassParams *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (g_class),
158 GST_QT_MUX_PARAMS_QDATA);
159 g_assert (params != NULL);
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);
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);
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);
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);
190 klass->format = params->prop->format;
194 gst_qt_mux_class_init (GstQTMuxClass * klass)
196 GObjectClass *gobject_class;
197 GstElementClass *gstelement_class;
199 gobject_class = (GObjectClass *) klass;
200 gstelement_class = (GstElementClass *) klass;
202 parent_class = g_type_class_peek_parent (klass);
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;
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));
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);
241 gst_qt_mux_pad_reset (GstQTPad * qtpad)
244 qtpad->is_out_of_order = FALSE;
245 qtpad->have_dts = FALSE;
246 qtpad->sample_size = 0;
251 gst_buffer_replace (&qtpad->last_buf, NULL);
253 /* reference owned elsewhere */
258 * Takes GstQTMux back to its initial state
261 gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
265 qtmux->state = GST_QT_MUX_STATE_NONE;
266 qtmux->header_size = 0;
267 qtmux->mdat_size = 0;
271 atom_ftyp_free (qtmux->ftyp);
275 atom_moov_free (qtmux->moov);
278 if (qtmux->fast_start_file) {
279 fclose (qtmux->fast_start_file);
280 qtmux->fast_start_file = NULL;
282 gst_tag_setter_reset_tags (GST_TAG_SETTER (qtmux));
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);
289 /* hm, moov_free above yanked the traks away from us,
290 * so do not free, but do clear */
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;
300 qtpad->trak = atom_trak_new (qtmux->context);
301 atom_moov_add_trak (qtmux->moov, qtpad->trak);
307 gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
309 GstElementClass *klass = GST_ELEMENT_CLASS (qtmux_klass);
310 GstPadTemplate *templ;
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);
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);
325 /* properties set to default upon construction */
327 /* always need this */
329 atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format));
331 /* internals to initial state */
332 gst_qt_mux_reset (qtmux, TRUE);
337 gst_qt_mux_finalize (GObject * object)
339 GstQTMux *qtmux = GST_QT_MUX_CAST (object);
341 gst_qt_mux_reset (qtmux, FALSE);
343 if (qtmux->fast_start_file_path)
344 g_free (qtmux->fast_start_file_path);
346 atoms_context_free (qtmux->context);
347 gst_object_unref (qtmux->collect);
349 G_OBJECT_CLASS (parent_class)->finalize (object);
353 gst_qt_mux_add_mp4_tag (GstQTMux * qtmux, const GstTagList * list,
354 const char *tag, const char *tag2, guint32 fourcc)
356 switch (gst_tag_get_type (tag)) {
362 if (!gst_tag_list_get_string (list, tag, &str) || !str)
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);
375 if (!gst_tag_list_get_double (list, tag, &value))
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);
382 /* paired unsigned integers */
388 if (!gst_tag_list_get_uint (list, tag, &value) ||
389 !gst_tag_list_get_uint (list, tag2, &count))
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));
398 g_assert_not_reached ();
404 gst_qt_mux_add_mp4_date (GstQTMux * qtmux, const GstTagList * list,
405 const char *tag, const char *tag2, guint32 fourcc)
413 g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_DATE);
415 if (!gst_tag_list_get_date (list, tag, &date) || !date)
418 year = g_date_get_year (date);
419 month = g_date_get_month (date);
420 day = g_date_get_day (date);
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");
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);
435 gst_qt_mux_add_mp4_cover (GstQTMux * qtmux, const GstTagList * list,
436 const char *tag, const char *tag2, guint32 fourcc)
438 GValue value = { 0, };
441 GstStructure *structure;
444 g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_BUFFER);
446 if (!gst_tag_list_copy_value (&value, list, tag))
449 buf = gst_value_get_buffer (&value);
453 caps = gst_buffer_get_caps (buf);
455 GST_WARNING_OBJECT (qtmux, "preview image without caps");
459 GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);
461 structure = gst_caps_get_structure (caps, 0);
462 if (gst_structure_has_name (structure, "image/jpeg"))
464 else if (gst_structure_has_name (structure, "image/png"))
466 gst_caps_unref (caps);
469 GST_WARNING_OBJECT (qtmux, "preview image format not supported");
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));
478 g_value_unset (&value);
482 gst_qt_mux_add_3gp_str (GstQTMux * qtmux, const GstTagList * list,
483 const char *tag, const char *tag2, guint32 fourcc)
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);
491 if (!gst_tag_list_get_string (list, tag, &str) || !str)
495 if (!gst_tag_list_get_uint (list, tag2, &number))
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);
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);
512 gst_qt_mux_add_3gp_date (GstQTMux * qtmux, const GstTagList * list,
513 const char *tag, const char *tag2, guint32 fourcc)
518 g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_DATE);
520 if (!gst_tag_list_get_date (list, tag, &date) || !date)
523 year = g_date_get_year (date);
525 if (year == G_DATE_BAD_YEAR) {
526 GST_WARNING_OBJECT (qtmux, "invalid date in tag");
530 GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %d", year);
531 atom_moov_add_3gp_uint_tag (qtmux->moov, fourcc, year);
535 gst_qt_mux_add_3gp_location (GstQTMux * qtmux, const GstTagList * list,
536 const char *tag, const char *tag2, guint32 fourcc)
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;
544 g_return_if_fail (strcmp (tag, GST_TAG_GEO_LOCATION_NAME) == 0);
546 ret = gst_tag_list_get_string (list, tag, &location);
547 ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LONGITUDE,
549 ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LATITUDE,
551 ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_ELEVATION,
558 len = strlen (location);
561 /* role + (long, lat, alt) + body + notes */
562 size += 1 + 3 * 4 + 1 + 1;
564 data = ddata = g_malloc (size);
567 GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
570 memcpy (data + 2, location, len);
571 GST_WRITE_UINT8 (data + 2 + len, 0);
574 GST_WRITE_UINT8 (data, 0);
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);
582 GST_DEBUG_OBJECT (qtmux, "Adding tag 'loci'");
583 atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
588 gst_qt_mux_add_3gp_keywords (GstQTMux * qtmux, const GstTagList * list,
589 const char *tag, const char *tag2, guint32 fourcc)
591 gchar *keywords = NULL;
592 guint8 *data, *ddata;
596 g_return_if_fail (strcmp (tag, GST_TAG_KEYWORDS) == 0);
598 if (!gst_tag_list_get_string (list, tag, &keywords) || !keywords)
601 kwds = g_strsplit (keywords, ",", 0);
604 for (i = 0; kwds[i]; i++) {
605 /* size byte + null-terminator */
606 size += strlen (kwds[i]) + 1 + 1;
609 /* language tag + count + keywords */
612 data = ddata = g_malloc (size);
615 GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
617 GST_WRITE_UINT8 (data + 2, i);
620 for (i = 0; kwds[i]; ++i) {
621 gint len = strlen (kwds[i]);
623 GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
624 GST_FOURCC_ARGS (fourcc), kwds[i]);
626 GST_WRITE_UINT8 (data, len + 1);
627 memcpy (data + 1, kwds[i], len + 1);
633 atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
638 typedef void (*GstQTMuxAddTagFunc) (GstQTMux * mux, const GstTagList * list,
639 const char *tag, const char *tag2, guint32 fourcc);
642 * Struct to record mappings from gstreamer tags to fourcc codes
644 typedef struct _GstTagToFourcc
648 const gchar *gsttag2;
649 const GstQTMuxAddTagFunc func;
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},
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},
688 /* qtdemux produces these for atoms it cannot parse */
689 #define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag"
692 gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list)
694 GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
697 const gchar *tag, *tag2;
698 const GstTagToFourcc *tag_matches;
700 switch (qtmux_klass->format) {
701 case GST_QT_MUX_FORMAT_3GP:
702 tag_matches = tag_matches_3gp;
704 case GST_QT_MUX_FORMAT_MJ2:
708 /* sort of iTunes style for mp4 and QT (?) */
709 tag_matches = tag_matches_mp4;
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;
721 g_assert (tag_matches[i].func);
722 tag_matches[i].func (qtmux, list, tag, tag2, fourcc);
725 /* add unparsed blobs if present */
726 if (gst_tag_exists (GST_QT_DEMUX_PRIVATE_TAG)) {
729 num_tags = gst_tag_list_get_tag_size (list, GST_QT_DEMUX_PRIVATE_TAG);
730 for (i = 0; i < num_tags; ++i) {
733 GstCaps *caps = NULL;
735 val = gst_tag_list_get_value_index (list, GST_QT_DEMUX_PRIVATE_TAG, i);
736 buf = (GstBuffer *) gst_value_get_mini_object (val);
738 if (buf && (caps = gst_buffer_get_caps (buf))) {
740 const gchar *style = NULL;
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));
757 gst_caps_unref (caps);
766 * Gets the tagsetter iface taglist and puts the known tags
767 * into the output stream
770 gst_qt_mux_setup_metadata (GstQTMux * qtmux)
772 const GstTagList *tags;
774 tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (qtmux));
776 GST_LOG_OBJECT (qtmux, "tags: %" GST_PTR_FORMAT, tags);
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);
782 GST_DEBUG_OBJECT (qtmux, "No tags received");
787 gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset,
794 g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
796 data = GST_BUFFER_DATA (buf);
797 size = GST_BUFFER_SIZE (buf);
799 GST_LOG_OBJECT (qtmux, "sending buffer size %d", size);
801 if (mind_fast && qtmux->fast_start_file) {
804 GST_LOG_OBJECT (qtmux, "to temporary file");
805 ret = fwrite (data, sizeof (guint8), size, qtmux->fast_start_file);
806 gst_buffer_unref (buf);
812 GST_LOG_OBJECT (qtmux, "downstream");
814 gst_buffer_set_caps (buf, GST_PAD_CAPS (qtmux->srcpad));
815 res = gst_pad_push (qtmux->srcpad, buf);
826 GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
827 ("Failed to write to temporary file"), GST_ERROR_SYSTEM);
828 return GST_FLOW_ERROR;
833 gst_qt_mux_send_buffered_data (GstQTMux * qtmux, guint64 * offset)
835 GstFlowReturn ret = GST_FLOW_OK;
836 GstBuffer *buf = NULL;
838 if (fflush (qtmux->fast_start_file))
842 if (fseeko (qtmux->fast_start_file, (off_t) 0, SEEK_SET) != 0)
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)
849 if (fseek (qtmux->fast_start_file, (long) 0, SEEK_SET) != 0)
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) {
859 const int bufsize = 4096;
861 buf = gst_buffer_new_and_alloc (bufsize);
862 r = fread (GST_BUFFER_DATA (buf), sizeof (guint8), bufsize,
863 qtmux->fast_start_file);
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);
872 gst_buffer_unref (buf);
875 /* best cleaning up effort, eat possible error */
876 fclose (qtmux->fast_start_file);
877 qtmux->fast_start_file = NULL;
879 /* FIXME maybe delete temporary file, or let the system handle that ? */
886 GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
887 ("Failed to flush temporary file"), GST_ERROR_SYSTEM);
888 ret = GST_FLOW_ERROR;
893 GST_ELEMENT_ERROR (qtmux, RESOURCE, SEEK,
894 ("Failed to seek temporary file"), GST_ERROR_SYSTEM);
895 ret = GST_FLOW_ERROR;
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.
908 gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size)
915 GST_DEBUG_OBJECT (qtmux, "Sending mdat's atom header, "
916 "size %" G_GUINT64_FORMAT, size);
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;
924 node_header->extended_size = size;
927 if (atom_copy_data (node_header, &data, &size, &offset) == 0)
928 goto serialize_error;
930 buf = gst_buffer_new ();
931 GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
932 GST_BUFFER_SIZE (buf) = offset;
934 g_free (node_header);
936 GST_LOG_OBJECT (qtmux, "Pushing mdat start");
937 return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
942 GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
943 ("Failed to serialize ftyp"));
944 return GST_FLOW_ERROR;
949 * We get the position of the mdat size field, seek back to it
950 * and overwrite with the real value
953 gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos,
954 guint64 mdat_size, guint64 * offset)
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);
964 buf = gst_buffer_new_and_alloc (sizeof (guint64));
965 GST_WRITE_UINT64_BE (GST_BUFFER_DATA (buf), mdat_size);
967 return gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
971 gst_qt_mux_stop_file (GstQTMux * qtmux)
973 gboolean ret = GST_FLOW_OK;
974 GstBuffer *buffer = NULL;
975 guint64 offset = 0, size = 0;
981 GST_DEBUG_OBJECT (qtmux, "Updating remaining values and sending last data");
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;
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));
998 GST_OBJECT_LOCK (qtmux);
999 timescale = qtmux->timescale;
1000 large_file = qtmux->large_file;
1001 GST_OBJECT_UNLOCK (qtmux);
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,
1009 atom_moov_update_timescale (qtmux->moov, timescale);
1010 atom_moov_set_64bits (qtmux->moov, large_file);
1011 atom_moov_update_duration (qtmux->moov);
1013 /* tags into file metadata */
1014 gst_qt_mux_setup_metadata (qtmux);
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 */
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,
1025 offset += qtmux->header_size + MDAT_ATOM_HEADER_SIZE;
1027 offset = qtmux->header_size;
1028 atom_moov_chunks_add_offset (qtmux->moov, offset);
1030 /* serialize moov */
1033 GST_LOG_OBJECT (qtmux, "Copying movie header into buffer");
1034 ret = atom_moov_copy_data (qtmux->moov, &data, &size, &offset);
1036 goto serialize_error;
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);
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)
1054 ret = gst_qt_mux_send_buffered_data (qtmux, NULL);
1055 if (ret != GST_FLOW_OK)
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 */
1071 gst_buffer_unref (buffer);
1072 GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1073 ("Failed to serialize moov"));
1074 return GST_FLOW_ERROR;
1078 static GstFlowReturn
1079 gst_qt_mux_send_ftyp (GstQTMux * qtmux, guint64 * off)
1082 guint64 size = 0, offset = 0;
1083 guint8 *data = NULL;
1085 GST_DEBUG_OBJECT (qtmux, "Sending ftyp atom");
1087 if (!atom_ftyp_copy_data (qtmux->ftyp, &data, &size, &offset))
1088 goto serialize_error;
1090 buf = gst_buffer_new ();
1091 GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
1092 GST_BUFFER_SIZE (buf) = offset;
1094 GST_LOG_OBJECT (qtmux, "Pushing ftyp");
1095 return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
1100 GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1101 ("Failed to serialize ftyp"));
1102 return GST_FLOW_ERROR;
1106 static GstFlowReturn
1107 gst_qt_mux_start_file (GstQTMux * qtmux)
1109 GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1110 GstFlowReturn ret = GST_FLOW_OK;
1111 guint32 major, version;
1115 GST_DEBUG_OBJECT (qtmux, "starting file");
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));
1121 /* init and send context and ftyp based on current property state */
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);
1130 ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE);
1131 if (ret != GST_FLOW_OK)
1134 ret = gst_qt_mux_send_ftyp (qtmux, &qtmux->header_size);
1135 if (ret != GST_FLOW_OK)
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)
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);
1149 GST_OBJECT_UNLOCK (qtmux);
1157 GST_ELEMENT_ERROR (qtmux, RESOURCE, OPEN_READ_WRITE,
1158 (("Could not open temporary file \"%s\""), qtmux->fast_start_file_path),
1160 GST_OBJECT_UNLOCK (qtmux);
1161 return GST_FLOW_ERROR;
1166 * Here we push the buffer and update the tables in the track atoms
1168 static GstFlowReturn
1169 gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
1171 GstBuffer *last_buf = NULL;
1172 GstClockTime duration;
1173 guint nsamples, sample_size;
1174 guint64 scaled_duration, chunk_offset;
1176 gint64 pts_offset = 0;
1177 gboolean sync = FALSE, do_pts = FALSE;
1180 goto not_negotiated;
1182 last_buf = pad->last_buf;
1183 if (last_buf == NULL) {
1184 #ifndef GST_DISABLE_GST_DEBUG
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));
1190 GST_LOG_OBJECT (qtmux,
1191 "Pad %s has no previous buffer stored, storing now",
1192 GST_PAD_NAME (pad->collect.pad));
1195 pad->last_buf = buf;
1198 gst_buffer_ref (last_buf);
1200 /* fall back to duration if:
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 */
1211 GST_WARNING_OBJECT (qtmux, "no duration for last buffer");
1212 /* iso spec recommends some small value, try 0 */
1215 duration = GST_BUFFER_DURATION (last_buf);
1218 duration = GST_BUFFER_TIMESTAMP (buf) - GST_BUFFER_TIMESTAMP (last_buf);
1221 gst_buffer_replace (&pad->last_buf, buf);
1223 last_dts = gst_util_uint64_scale (pad->last_dts,
1224 atom_trak_get_timescale (pad->trak), GST_SECOND);
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;
1240 sample_size = GST_BUFFER_SIZE (last_buf);
1241 if (pad->have_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);
1248 scaled_dts = gst_util_uint64_scale (pad->last_dts,
1249 atom_trak_get_timescale (pad->trak), GST_SECOND);
1251 scaled_duration = scaled_dts - last_dts;
1252 last_dts = scaled_dts;
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;
1263 chunk_offset = qtmux->mdat_size;
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);
1273 /* might be a sync sample */
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));
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) {
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);
1297 GST_LOG_OBJECT (qtmux, "Adding ctts entry for pad %s: %" G_GINT64_FORMAT,
1298 GST_PAD_NAME (pad->collect.pad), pts_offset);
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);
1307 gst_buffer_unref (buf);
1309 return gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE);
1315 gst_buffer_unref (buf);
1316 gst_buffer_unref (last_buf);
1317 return GST_FLOW_ERROR;
1321 GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1322 ("Failed to determine time to mux."));
1327 GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1328 ("Audio buffer contains fragmented sample."));
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;
1341 static GstFlowReturn
1342 gst_qt_mux_collected (GstCollectPads * pads, gpointer user_data)
1344 GstFlowReturn ret = GST_FLOW_OK;
1345 GstQTMux *qtmux = GST_QT_MUX_CAST (user_data);
1347 GstQTPad *best_pad = NULL;
1348 GstClockTime time, best_time = GST_CLOCK_TIME_NONE;
1351 if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_STARTED)) {
1352 if ((ret = gst_qt_mux_start_file (qtmux)) != GST_FLOW_OK)
1355 qtmux->state = GST_QT_MUX_STATE_DATA;
1358 if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS))
1359 return GST_FLOW_UNEXPECTED;
1361 /* select the best buffer */
1362 walk = qtmux->collect->data;
1365 GstCollectData *data;
1367 data = (GstCollectData *) walk->data;
1368 pad = (GstQTPad *) data;
1370 walk = g_slist_next (walk);
1372 buf = gst_collect_pads_peek (pads, data);
1374 GST_LOG_OBJECT (qtmux, "Pad %s has no buffers",
1375 GST_PAD_NAME (pad->collect.pad));
1378 time = GST_BUFFER_TIMESTAMP (buf);
1379 gst_buffer_unref (buf);
1381 if (best_pad == NULL || !GST_CLOCK_TIME_IS_VALID (time) ||
1382 (GST_CLOCK_TIME_IS_VALID (best_time) && time < best_time)) {
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);
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;
1399 qtmux->state = GST_QT_MUX_STATE_EOS;
1406 gst_qt_mux_audio_sink_set_caps (GstPad * pad, GstCaps * caps)
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;
1421 /* find stream data */
1422 qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
1425 /* does not go well to renegotiate stream mid-way */
1427 goto refuse_renegotiation;
1429 GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
1430 GST_DEBUG_PAD_NAME (pad), caps);
1432 format = qtmux_klass->format;
1433 structure = gst_caps_get_structure (caps, 0);
1434 mimetype = gst_structure_get_name (structure);
1437 if (!gst_structure_get_int (structure, "channels", &channels) ||
1438 !gst_structure_get_int (structure, "rate", &rate)) {
1443 value = gst_structure_get_value (structure, "codec_data");
1445 codec_data = gst_value_get_buffer (value);
1447 qtpad->is_out_of_order = FALSE;
1448 qtpad->have_dts = FALSE;
1450 /* set common properties */
1451 entry.sample_rate = rate;
1452 entry.channels = channels;
1454 entry.sample_size = 16;
1455 /* this is the typical compressed case */
1456 if (format == GST_QT_MUX_FORMAT_QT) {
1458 entry.compression_id = -2;
1461 /* now map onto a fourcc, and some extra properties */
1462 if (strcmp (mimetype, "audio/mpeg") == 0) {
1463 gint mpegversion = 0;
1466 gst_structure_get_int (structure, "mpegversion", &mpegversion);
1467 switch (mpegversion) {
1469 gst_structure_get_int (structure, "layer", &layer);
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;
1477 entry.fourcc = FOURCC_mp4a;
1479 build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG1_P3,
1480 ESDS_STREAM_TYPE_AUDIO, codec_data);
1482 entry.samples_per_packet = 1152;
1483 entry.bytes_per_sample = 2;
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");
1493 guint8 profile = GST_READ_UINT8 (GST_BUFFER_DATA (codec_data));
1495 /* warn if not Low Complexity profile */
1498 GST_WARNING_OBJECT (qtmux,
1499 "non-LC AAC may not run well on (Apple) QuickTime/iTunes");
1501 if (format == GST_QT_MUX_FORMAT_QT)
1502 ext_atom = build_mov_aac_extension (qtpad->trak, codec_data);
1505 build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P3,
1506 ESDS_STREAM_TYPE_AUDIO, codec_data);
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) {
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");
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!");
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 */
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;
1562 if (width == 8 && depth == 8) {
1563 /* fall back to old 8-bit version */
1564 entry.fourcc = FOURCC_raw_;
1566 entry.compression_id = 0;
1567 entry.sample_size = 8;
1569 GST_DEBUG_OBJECT (qtmux, "non 8-bit PCM must be signed");
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;
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);
1593 gst_object_unref (qtmux);
1599 GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
1600 GST_PAD_NAME (pad), caps);
1601 gst_object_unref (qtmux);
1604 refuse_renegotiation:
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);
1614 /* scale rate up or down by factor of 10 to fit into [1000,10000] interval */
1616 adjust_rate (guint64 rate)
1618 while (rate >= 10000)
1624 return (guint32) rate;
1628 gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps)
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;
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;
1646 /* find stream data */
1647 qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
1650 /* does not go well to renegotiate stream mid-way */
1652 goto refuse_renegotiation;
1654 GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
1655 GST_DEBUG_PAD_NAME (pad), caps);
1657 format = qtmux_klass->format;
1658 structure = gst_caps_get_structure (caps, 0);
1659 mimetype = gst_structure_get_name (structure);
1661 /* required parts */
1662 if (!gst_structure_get_int (structure, "width", &width) ||
1663 !gst_structure_get_int (structure, "height", &height))
1668 /* works as a default timebase */
1669 framerate_num = 10000;
1671 gst_structure_get_fraction (structure, "framerate", &framerate_num,
1673 gst_structure_get_int (structure, "depth", &depth);
1674 value = gst_structure_get_value (structure, "codec_data");
1676 codec_data = gst_value_get_buffer (value);
1680 gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_num,
1683 qtpad->is_out_of_order = FALSE;
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,
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;
1701 /* sync entries by default */
1704 /* now map onto a fourcc, and some extra properties */
1705 if (strcmp (mimetype, "video/x-raw-rgb") == 0) {
1708 entry.fourcc = FOURCC_raw_;
1709 gst_structure_get_int (structure, "bpp", &bpp);
1712 } else if (strcmp (mimetype, "video/x-raw-yuv") == 0) {
1716 gst_structure_get_fourcc (structure, "format", &format);
1718 case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
1721 entry.fourcc = FOURCC_2vuy;
1722 entry.depth = depth;
1725 } else if (strcmp (mimetype, "video/x-h263") == 0) {
1726 if (format == GST_QT_MUX_FORMAT_QT)
1727 entry.fourcc = FOURCC_h263;
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) {
1735 if (strcmp (mimetype, "video/x-divx") == 0) {
1736 gst_structure_get_int (structure, "divxversion", &version);
1737 version = version == 5 ? 1 : 0;
1739 gst_structure_get_int (structure, "mpegversion", &version);
1740 version = version == 4 ? 1 : 0;
1743 entry.fourcc = FOURCC_mp4v;
1745 build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P2,
1746 ESDS_STREAM_TYPE_VISUAL, codec_data);
1748 GST_WARNING_OBJECT (qtmux, "no codec_data for MPEG4 video; "
1749 "output might not play in Apple QuickTime (try global-headers?)");
1751 } else if (strcmp (mimetype, "video/x-h264") == 0) {
1752 entry.fourcc = FOURCC_avc1;
1753 qtpad->is_out_of_order = TRUE;
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) {
1759 gboolean pal = TRUE;
1762 if (framerate_num != 25 || framerate_den != 1)
1764 gst_structure_get_int (structure, "dvversion", &version);
1765 /* fall back to typical one */
1771 entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', 'p');
1773 entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', ' ');
1777 entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'p');
1779 entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'n');
1782 GST_WARNING_OBJECT (qtmux, "unrecognized dv version");
1785 } else if (strcmp (mimetype, "image/jpeg") == 0) {
1786 entry.fourcc = FOURCC_jpeg;
1788 } else if (strcmp (mimetype, "image/x-j2c") == 0) {
1791 entry.fourcc = FOURCC_mjp2;
1793 if (!gst_structure_get_fourcc (structure, "fourcc", &fourcc) ||
1795 build_jp2h_extension (qtpad->trak, width, height, fourcc))) {
1796 GST_DEBUG_OBJECT (qtmux, "missing or invalid fourcc in jp2 caps");
1799 } else if (strcmp (mimetype, "video/x-qt-part") == 0) {
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) {
1809 gst_structure_get_fourcc (structure, "format", &fourcc);
1810 entry.fourcc = fourcc;
1811 qtpad->is_out_of_order = TRUE;
1812 qtpad->have_dts = TRUE;
1818 /* ok, set the pad info accordingly */
1819 qtpad->fourcc = entry.fourcc;
1821 atom_trak_set_video_type (qtpad->trak, qtmux->context, &entry, rate,
1824 gst_object_unref (qtmux);
1830 GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
1831 GST_PAD_NAME (pad), caps);
1832 gst_object_unref (qtmux);
1835 refuse_renegotiation:
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);
1846 gst_qt_mux_sink_event (GstPad * pad, GstEvent * event)
1851 qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1852 switch (GST_EVENT_TYPE (event)) {
1853 case GST_EVENT_TAG:{
1855 GstTagSetter *setter = GST_TAG_SETTER (qtmux);
1856 const GstTagMergeMode mode = gst_tag_setter_get_tag_merge_mode (setter);
1858 GST_DEBUG_OBJECT (qtmux, "received tag event");
1859 gst_event_parse_tag (event, &list);
1860 gst_tag_setter_merge_tags (setter, list, mode);
1867 ret = qtmux->collect_event (pad, event);
1868 gst_object_unref (qtmux);
1874 gst_qt_mux_release_pad (GstElement * element, GstPad * pad)
1876 GstQTMux *mux = GST_QT_MUX_CAST (element);
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);
1884 gst_qt_mux_request_new_pad (GstElement * element,
1885 GstPadTemplate * templ, const gchar * name)
1887 GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
1888 GstQTMux *qtmux = GST_QT_MUX_CAST (element);
1889 GstQTPad *collect_pad;
1893 GST_DEBUG_OBJECT (qtmux, "Requested pad: %s", GST_STR_NULL (name));
1895 if (qtmux->state != GST_QT_MUX_STATE_NONE) {
1896 GST_WARNING_OBJECT (qtmux, "Not providing request pad after stream start.");
1900 if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
1902 } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
1905 GST_WARNING_OBJECT (qtmux, "This is not our template!");
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));
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);
1919 /* set up pad functions */
1921 gst_pad_set_setcaps_function (newpad,
1922 GST_DEBUG_FUNCPTR (gst_qt_mux_audio_sink_set_caps));
1924 gst_pad_set_setcaps_function (newpad,
1925 GST_DEBUG_FUNCPTR (gst_qt_mux_video_sink_set_caps));
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.
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));
1935 gst_pad_set_active (newpad, TRUE);
1936 gst_element_add_pad (element, newpad);
1942 gst_qt_mux_get_property (GObject * object,
1943 guint prop_id, GValue * value, GParamSpec * pspec)
1945 GstQTMux *qtmux = GST_QT_MUX_CAST (object);
1947 GST_OBJECT_LOCK (qtmux);
1949 case PROP_LARGE_FILE:
1950 g_value_set_boolean (value, qtmux->large_file);
1952 case PROP_MOVIE_TIMESCALE:
1953 g_value_set_uint (value, qtmux->timescale);
1956 g_value_set_boolean (value, qtmux->guess_pts);
1958 case PROP_FAST_START:
1959 g_value_set_boolean (value, qtmux->fast_start);
1961 case PROP_FAST_START_TEMP_FILE:
1962 g_value_set_string (value, qtmux->fast_start_file_path);
1965 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1968 GST_OBJECT_UNLOCK (qtmux);
1972 gst_qt_mux_generate_fast_start_file_path (GstQTMux * qtmux)
1976 if (qtmux->fast_start_file_path) {
1977 g_free (qtmux->fast_start_file_path);
1978 qtmux->fast_start_file_path = NULL;
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);
1987 gst_qt_mux_set_property (GObject * object,
1988 guint prop_id, const GValue * value, GParamSpec * pspec)
1990 GstQTMux *qtmux = GST_QT_MUX_CAST (object);
1992 GST_OBJECT_LOCK (qtmux);
1994 case PROP_LARGE_FILE:
1995 qtmux->large_file = g_value_get_boolean (value);
1997 case PROP_MOVIE_TIMESCALE:
1998 qtmux->timescale = g_value_get_uint (value);
2001 qtmux->guess_pts = g_value_get_boolean (value);
2003 case PROP_FAST_START:
2004 qtmux->fast_start = g_value_get_boolean (value);
2006 case PROP_FAST_START_TEMP_FILE:
2007 if (qtmux->fast_start_file_path) {
2008 g_free (qtmux->fast_start_file_path);
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);
2017 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2020 GST_OBJECT_UNLOCK (qtmux);
2023 static GstStateChangeReturn
2024 gst_qt_mux_change_state (GstElement * element, GstStateChange transition)
2026 GstStateChangeReturn ret;
2027 GstQTMux *qtmux = GST_QT_MUX_CAST (element);
2029 switch (transition) {
2030 case GST_STATE_CHANGE_NULL_TO_READY:
2032 case GST_STATE_CHANGE_READY_TO_PAUSED:
2033 gst_collect_pads_start (qtmux->collect);
2034 qtmux->state = GST_QT_MUX_STATE_STARTED;
2036 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
2038 case GST_STATE_CHANGE_PAUSED_TO_READY:
2039 gst_collect_pads_stop (qtmux->collect);
2045 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2047 switch (transition) {
2048 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
2050 case GST_STATE_CHANGE_PAUSED_TO_READY:
2051 gst_qt_mux_reset (qtmux, TRUE);
2053 case GST_STATE_CHANGE_READY_TO_NULL:
2064 gst_qt_mux_register (GstPlugin * plugin)
2066 GTypeInfo typeinfo = {
2067 sizeof (GstQTMuxClass),
2068 (GBaseInitFunc) gst_qt_mux_base_init,
2070 (GClassInitFunc) gst_qt_mux_class_init,
2075 (GInstanceInitFunc) gst_qt_mux_init,
2077 static const GInterfaceInfo tag_setter_info = {
2081 GstQTMuxFormat format;
2082 GstQTMuxClassParams *params;
2085 GST_LOG ("Registering muxers");
2088 GstQTMuxFormatProp *prop;
2090 prop = &gst_qt_mux_format_list[i];
2091 format = prop->format;
2092 if (format == GST_QT_MUX_FORMAT_NONE)
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);
2102 /* create the type now */
2103 type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo,
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);
2108 if (!gst_element_register (plugin, prop->name, GST_RANK_PRIMARY, type))
2114 GST_LOG ("Finished registering muxers");
2120 gst_qt_mux_plugin_init (GstPlugin * plugin)
2122 GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
2124 return gst_qt_mux_register (plugin);
2127 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
2130 "Quicktime Muxer plugin",
2131 gst_qt_mux_plugin_init, VERSION, "LGPL", "gsoc2008 package",
2132 "embedded.ufcg.edu.br")