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);
352 /* FIXME approach below is pretty Apple/MOV/MP4/iTunes specific,
353 * and as such does not comply with e.g. 3GPP specs */
356 * Struct to record mappings from gstreamer tags to fourcc codes
358 typedef struct _GstTagToFourcc
362 const gchar *gsttag2;
365 /* tag list tags to fourcc matching */
366 static const GstTagToFourcc tag_matches[] = {
367 {FOURCC__alb, GST_TAG_ALBUM,},
368 {FOURCC__ART, GST_TAG_ARTIST,},
369 {FOURCC__cmt, GST_TAG_COMMENT,},
370 {FOURCC__wrt, GST_TAG_COMPOSER,},
371 {FOURCC__gen, GST_TAG_GENRE,},
372 {FOURCC__nam, GST_TAG_TITLE,},
373 {FOURCC__des, GST_TAG_DESCRIPTION,},
374 {FOURCC__too, GST_TAG_ENCODER,},
375 {FOURCC_cprt, GST_TAG_COPYRIGHT,},
376 {FOURCC_keyw, GST_TAG_KEYWORDS,},
377 {FOURCC__day, GST_TAG_DATE,},
378 {FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE,},
379 {FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT},
380 {FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT},
381 {FOURCC_covr, GST_TAG_PREVIEW_IMAGE,},
385 /* qtdemux produces these for atoms it cannot parse */
386 #define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag"
389 gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list)
393 const gchar *tag, *tag2;
395 for (i = 0; tag_matches[i].fourcc; i++) {
396 fourcc = tag_matches[i].fourcc;
397 tag = tag_matches[i].gsttag;
398 tag2 = tag_matches[i].gsttag2;
400 switch (gst_tag_get_type (tag)) {
406 if (!gst_tag_list_get_string (list, tag, &str) || !str)
408 GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
409 GST_FOURCC_ARGS (fourcc), str);
410 atom_moov_add_str_tag (qtmux->moov, fourcc, str);
419 if (!gst_tag_list_get_double (list, tag, &value))
421 GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
422 GST_FOURCC_ARGS (fourcc), (gint) value);
423 atom_moov_add_uint_tag (qtmux->moov, fourcc, 21, (gint) value);
426 /* paired unsigned integers */
432 if (!gst_tag_list_get_uint (list, tag, &value) ||
433 !gst_tag_list_get_uint (list, tag2, &count))
435 GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u",
436 GST_FOURCC_ARGS (fourcc), value, count);
437 atom_moov_add_uint_tag (qtmux->moov, fourcc, 0,
438 value << 16 | (count & 0xFFFF));
443 if (gst_tag_get_type (tag) == GST_TYPE_DATE) {
450 if (!gst_tag_list_get_date (list, tag, &date) || !date)
452 year = g_date_get_year (date);
453 month = g_date_get_month (date);
454 day = g_date_get_day (date);
456 if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH &&
457 day == G_DATE_BAD_DAY) {
458 GST_WARNING_OBJECT (qtmux, "invalid date in tag");
462 str = g_strdup_printf ("%u-%u-%u", year, month, day);
463 GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
464 GST_FOURCC_ARGS (fourcc), str);
465 atom_moov_add_str_tag (qtmux->moov, fourcc, str);
466 } else if (gst_tag_get_type (tag) == GST_TYPE_BUFFER) {
467 GValue value = { 0, };
470 GstStructure *structure;
473 if (!gst_tag_list_copy_value (&value, list, tag))
476 buf = gst_value_get_buffer (&value);
480 caps = gst_buffer_get_caps (buf);
482 GST_WARNING_OBJECT (qtmux, "preview image without caps");
486 GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);
488 structure = gst_caps_get_structure (caps, 0);
489 if (gst_structure_has_name (structure, "image/jpeg"))
491 else if (gst_structure_has_name (structure, "image/png"))
493 gst_caps_unref (caps);
496 GST_WARNING_OBJECT (qtmux, "preview image format not supported");
500 GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT
501 " -> image size %d", GST_FOURCC_ARGS (fourcc),
502 GST_BUFFER_SIZE (buf));
503 atom_moov_add_tag (qtmux->moov, fourcc, flags, GST_BUFFER_DATA (buf),
504 GST_BUFFER_SIZE (buf));
506 g_value_unset (&value);
508 g_assert_not_reached ();
514 /* add unparsed blobs if present */
515 if (gst_tag_exists (GST_QT_DEMUX_PRIVATE_TAG)) {
518 num_tags = gst_tag_list_get_tag_size (list, GST_QT_DEMUX_PRIVATE_TAG);
519 for (i = 0; i < num_tags; ++i) {
522 GstCaps *caps = NULL;
524 val = gst_tag_list_get_value_index (list, GST_QT_DEMUX_PRIVATE_TAG, i);
525 buf = (GstBuffer *) gst_value_get_mini_object (val);
527 if (buf && (caps = gst_buffer_get_caps (buf))) {
529 const gchar *style = NULL;
531 GST_DEBUG_OBJECT (qtmux, "Found private tag %d/%d; size %d, caps %"
532 GST_PTR_FORMAT, i, num_tags, GST_BUFFER_SIZE (buf), caps);
533 s = gst_caps_get_structure (caps, 0);
534 if (s && (style = gst_structure_get_string (s, "style"))) {
535 /* FIXME make into a parameter */
536 if (strcmp (style, "itunes") == 0) {
537 GST_DEBUG_OBJECT (qtmux, "Adding private tag");
538 atom_moov_add_blob_tag (qtmux->moov, GST_BUFFER_DATA (buf),
539 GST_BUFFER_SIZE (buf));
542 gst_caps_unref (caps);
551 * Gets the tagsetter iface taglist and puts the known tags
552 * into the output stream
555 gst_qt_mux_setup_metadata (GstQTMux * qtmux)
557 const GstTagList *tags;
559 tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (qtmux));
561 GST_LOG_OBJECT (qtmux, "tags: %" GST_PTR_FORMAT, tags);
563 if (tags && !gst_tag_list_is_empty (tags)) {
564 GST_DEBUG_OBJECT (qtmux, "Formatting tags");
565 gst_qt_mux_add_metadata_tags (qtmux, tags);
567 GST_DEBUG_OBJECT (qtmux, "No tags received");
572 gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset,
579 g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
581 data = GST_BUFFER_DATA (buf);
582 size = GST_BUFFER_SIZE (buf);
584 GST_LOG_OBJECT (qtmux, "sending buffer size %d", size);
586 if (mind_fast && qtmux->fast_start_file) {
589 GST_LOG_OBJECT (qtmux, "to temporary file");
590 ret = fwrite (data, sizeof (guint8), size, qtmux->fast_start_file);
591 gst_buffer_unref (buf);
597 GST_LOG_OBJECT (qtmux, "downstream");
599 gst_buffer_set_caps (buf, GST_PAD_CAPS (qtmux->srcpad));
600 res = gst_pad_push (qtmux->srcpad, buf);
611 GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
612 ("Failed to write to temporary file"), GST_ERROR_SYSTEM);
613 return GST_FLOW_ERROR;
618 gst_qt_mux_send_buffered_data (GstQTMux * qtmux, guint64 * offset)
620 GstFlowReturn ret = GST_FLOW_OK;
621 GstBuffer *buf = NULL;
623 if (fflush (qtmux->fast_start_file))
627 if (fseeko (qtmux->fast_start_file, (off_t) 0, SEEK_SET) != 0)
629 #elif defined (G_OS_UNIX) || defined (G_OS_WIN32)
630 if (lseek (fileno (qtmux->fast_start_file), (off_t) 0,
631 SEEK_SET) == (off_t) - 1)
634 if (fseek (qtmux->fast_start_file, (long) 0, SEEK_SET) != 0)
638 /* hm, this could all take a really really long time,
639 * but there may not be another way to get moov atom first
640 * (somehow optimize copy?) */
641 GST_DEBUG_OBJECT (qtmux, "Sending buffered data");
642 while (ret == GST_FLOW_OK) {
644 const int bufsize = 4096;
646 buf = gst_buffer_new_and_alloc (bufsize);
647 r = fread (GST_BUFFER_DATA (buf), sizeof (guint8), bufsize,
648 qtmux->fast_start_file);
651 GST_BUFFER_SIZE (buf) = r;
652 GST_LOG_OBJECT (qtmux, "Pushing buffered buffer of size %d", r);
653 ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
657 gst_buffer_unref (buf);
660 /* best cleaning up effort, eat possible error */
661 fclose (qtmux->fast_start_file);
662 qtmux->fast_start_file = NULL;
664 /* FIXME maybe delete temporary file, or let the system handle that ? */
671 GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
672 ("Failed to flush temporary file"), GST_ERROR_SYSTEM);
673 ret = GST_FLOW_ERROR;
678 GST_ELEMENT_ERROR (qtmux, RESOURCE, SEEK,
679 ("Failed to seek temporary file"), GST_ERROR_SYSTEM);
680 ret = GST_FLOW_ERROR;
686 * Sends the initial mdat atom fields (size fields and fourcc type),
687 * the subsequent buffers are considered part of it's data.
688 * As we can't predict the amount of data that we are going to place in mdat
689 * we need to record the position of the size field in the stream so we can
690 * seek back to it later and update when the streams have finished.
693 gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size)
700 GST_DEBUG_OBJECT (qtmux, "Sending mdat's atom header, "
701 "size %" G_GUINT64_FORMAT, size);
703 node_header = g_malloc0 (sizeof (Atom));
704 node_header->type = FOURCC_mdat;
705 /* use extended size */
706 node_header->size = 1;
707 node_header->extended_size = 0;
709 node_header->extended_size = size;
712 if (atom_copy_data (node_header, &data, &size, &offset) == 0)
713 goto serialize_error;
715 buf = gst_buffer_new ();
716 GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
717 GST_BUFFER_SIZE (buf) = offset;
719 g_free (node_header);
721 GST_LOG_OBJECT (qtmux, "Pushing mdat start");
722 return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
727 GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
728 ("Failed to serialize ftyp"));
729 return GST_FLOW_ERROR;
734 * We get the position of the mdat size field, seek back to it
735 * and overwrite with the real value
738 gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos,
739 guint64 mdat_size, guint64 * offset)
744 /* seek and rewrite the header */
745 event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES,
746 mdat_pos, GST_CLOCK_TIME_NONE, 0);
747 gst_pad_push_event (qtmux->srcpad, event);
749 buf = gst_buffer_new_and_alloc (sizeof (guint64));
750 GST_WRITE_UINT64_BE (GST_BUFFER_DATA (buf), mdat_size);
752 return gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
756 gst_qt_mux_stop_file (GstQTMux * qtmux)
758 gboolean ret = GST_FLOW_OK;
759 GstBuffer *buffer = NULL;
760 guint64 offset = 0, size = 0;
766 GST_DEBUG_OBJECT (qtmux, "Updating remaining values and sending last data");
768 /* pushing last buffers for each pad */
769 for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
770 GstCollectData *cdata = (GstCollectData *) walk->data;
771 GstQTPad *qtpad = (GstQTPad *) cdata;
773 /* send last buffer */
774 GST_DEBUG_OBJECT (qtmux, "Sending the last buffer for pad %s",
775 GST_PAD_NAME (qtpad->collect.pad));
776 ret = gst_qt_mux_add_buffer (qtmux, qtpad, NULL);
777 if (ret != GST_FLOW_OK)
778 GST_DEBUG_OBJECT (qtmux, "Failed to send last buffer for %s, "
779 "flow return: %s", GST_PAD_NAME (qtpad->collect.pad),
780 gst_flow_get_name (ret));
783 GST_OBJECT_LOCK (qtmux);
784 timescale = qtmux->timescale;
785 large_file = qtmux->large_file;
786 GST_OBJECT_UNLOCK (qtmux);
788 /* inform lower layers of our property wishes, and determine duration.
789 * Let moov take care of this using its list of traks;
790 * so that released pads are also included */
791 GST_DEBUG_OBJECT (qtmux, "Large file support: %d", large_file);
792 GST_DEBUG_OBJECT (qtmux, "Updating timescale to %" G_GUINT32_FORMAT,
794 atom_moov_update_timescale (qtmux->moov, timescale);
795 atom_moov_set_64bits (qtmux->moov, large_file);
796 atom_moov_update_duration (qtmux->moov);
798 /* tags into file metadata */
799 gst_qt_mux_setup_metadata (qtmux);
801 /* if faststart, update the offset of the atoms in the movie with the offset
802 * that the movie headers before mdat will cause */
803 if (qtmux->fast_start_file) {
804 /* copy into NULL to obtain size */
806 if (!atom_moov_copy_data (qtmux->moov, NULL, &size, &offset))
807 goto serialize_error;
808 GST_DEBUG_OBJECT (qtmux, "calculated moov atom size %" G_GUINT64_FORMAT,
810 offset += qtmux->header_size + MDAT_ATOM_HEADER_SIZE;
812 offset = qtmux->header_size;
813 atom_moov_chunks_add_offset (qtmux->moov, offset);
818 GST_LOG_OBJECT (qtmux, "Copying movie header into buffer");
819 ret = atom_moov_copy_data (qtmux->moov, &data, &size, &offset);
821 goto serialize_error;
823 buffer = gst_buffer_new ();
824 GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer) = data;
825 GST_BUFFER_SIZE (buffer) = offset;
826 /* note: as of this point, we no longer care about tracking written data size,
827 * since there is no more use for it anyway */
828 GST_DEBUG_OBJECT (qtmux, "Pushing movie atoms");
829 gst_qt_mux_send_buffer (qtmux, buffer, NULL, FALSE);
831 /* total mdat size as of now also includes the atom header */
832 qtmux->mdat_size += MDAT_ATOM_HEADER_SIZE;
833 /* if needed, send mdat atom and move buffered data into it */
834 if (qtmux->fast_start_file) {
835 /* mdat size = accumulated (buffered data) + mdat atom header */
836 ret = gst_qt_mux_send_mdat_header (qtmux, NULL, qtmux->mdat_size);
837 if (ret != GST_FLOW_OK)
839 ret = gst_qt_mux_send_buffered_data (qtmux, NULL);
840 if (ret != GST_FLOW_OK)
843 /* mdata needs update iff not using faststart */
844 GST_DEBUG_OBJECT (qtmux, "updating mdata size");
845 ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
846 qtmux->mdat_size, NULL);
847 /* note; no seeking back to the end of file is done,
848 * since we longer write anything anyway */
856 gst_buffer_unref (buffer);
857 GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
858 ("Failed to serialize moov"));
859 return GST_FLOW_ERROR;
864 gst_qt_mux_send_ftyp (GstQTMux * qtmux, guint64 * off)
867 guint64 size = 0, offset = 0;
870 GST_DEBUG_OBJECT (qtmux, "Sending ftyp atom");
872 if (!atom_ftyp_copy_data (qtmux->ftyp, &data, &size, &offset))
873 goto serialize_error;
875 buf = gst_buffer_new ();
876 GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
877 GST_BUFFER_SIZE (buf) = offset;
879 GST_LOG_OBJECT (qtmux, "Pushing ftyp");
880 return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
885 GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
886 ("Failed to serialize ftyp"));
887 return GST_FLOW_ERROR;
892 gst_qt_mux_start_file (GstQTMux * qtmux)
894 GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
895 GstFlowReturn ret = GST_FLOW_OK;
896 guint32 major, version;
900 GST_DEBUG_OBJECT (qtmux, "starting file");
902 /* let downstream know we think in BYTES and expect to do seeking later on */
903 gst_pad_push_event (qtmux->srcpad,
904 gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0));
906 /* init and send context and ftyp based on current property state */
908 atom_ftyp_free (qtmux->ftyp);
909 gst_qt_mux_map_format_to_header (qtmux_klass->format, &prefix, &major,
910 &version, &comp, qtmux->moov);
911 qtmux->ftyp = atom_ftyp_new (qtmux->context, major, version, comp);
915 ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE);
916 if (ret != GST_FLOW_OK)
919 ret = gst_qt_mux_send_ftyp (qtmux, &qtmux->header_size);
920 if (ret != GST_FLOW_OK)
923 /* send mdat header if already needed, and mark position for later update */
924 GST_OBJECT_LOCK (qtmux);
925 if (qtmux->fast_start) {
926 qtmux->fast_start_file = g_fopen (qtmux->fast_start_file_path, "wb+");
927 if (!qtmux->fast_start_file)
930 ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0);
931 /* mdat size position = current header pos - extended header size */
932 qtmux->mdat_pos = qtmux->header_size - sizeof (guint64);
934 GST_OBJECT_UNLOCK (qtmux);
942 GST_ELEMENT_ERROR (qtmux, RESOURCE, OPEN_READ_WRITE,
943 (("Could not open temporary file \"%s\""), qtmux->fast_start_file_path),
945 GST_OBJECT_UNLOCK (qtmux);
946 return GST_FLOW_ERROR;
951 * Here we push the buffer and update the tables in the track atoms
954 gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
956 GstBuffer *last_buf = NULL;
957 GstClockTime duration;
958 guint nsamples, sample_size;
959 guint64 scaled_duration, chunk_offset;
961 gint64 pts_offset = 0;
962 gboolean sync = FALSE, do_pts = FALSE;
967 last_buf = pad->last_buf;
968 if (last_buf == NULL) {
969 #ifndef GST_DISABLE_GST_DEBUG
971 GST_DEBUG_OBJECT (qtmux, "Pad %s has no previous buffer stored and "
972 "received NULL buffer, doing nothing",
973 GST_PAD_NAME (pad->collect.pad));
975 GST_LOG_OBJECT (qtmux,
976 "Pad %s has no previous buffer stored, storing now",
977 GST_PAD_NAME (pad->collect.pad));
983 gst_buffer_ref (last_buf);
985 /* fall back to duration if:
987 * - this format has out of order buffers (e.g. MPEG-4),
988 * - lack of valid time forces fall back */
989 if (buf == NULL || pad->is_out_of_order ||
990 !GST_BUFFER_TIMESTAMP_IS_VALID (last_buf) ||
991 !GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
992 if (!GST_BUFFER_DURATION_IS_VALID (last_buf)) {
993 /* be forgiving for some possibly last upstream flushed buffer */
996 GST_WARNING_OBJECT (qtmux, "no duration for last buffer");
997 /* iso spec recommends some small value, try 0 */
1000 duration = GST_BUFFER_DURATION (last_buf);
1003 duration = GST_BUFFER_TIMESTAMP (buf) - GST_BUFFER_TIMESTAMP (last_buf);
1006 gst_buffer_replace (&pad->last_buf, buf);
1008 last_dts = gst_util_uint64_scale (pad->last_dts,
1009 atom_trak_get_timescale (pad->trak), GST_SECOND);
1011 /* raw audio has many samples per buffer (= chunk) */
1012 if (pad->sample_size) {
1013 sample_size = pad->sample_size;
1014 if (GST_BUFFER_SIZE (last_buf) % sample_size != 0)
1015 goto fragmented_sample;
1016 /* note: qt raw audio storage warps it implicitly into a timewise
1017 * perfect stream, discarding buffer times */
1018 nsamples = GST_BUFFER_SIZE (last_buf) / sample_size;
1019 duration = GST_BUFFER_DURATION (last_buf) / nsamples;
1020 /* timescale = samplerate */
1021 scaled_duration = 1;
1022 pad->last_dts += duration * nsamples;
1025 sample_size = GST_BUFFER_SIZE (last_buf);
1026 if (pad->have_dts) {
1028 pad->last_dts = GST_BUFFER_OFFSET_END (last_buf);
1029 if ((gint64) (pad->last_dts) < 0) {
1030 scaled_dts = -gst_util_uint64_scale (-pad->last_dts,
1031 atom_trak_get_timescale (pad->trak), GST_SECOND);
1033 scaled_dts = gst_util_uint64_scale (pad->last_dts,
1034 atom_trak_get_timescale (pad->trak), GST_SECOND);
1036 scaled_duration = scaled_dts - last_dts;
1037 last_dts = scaled_dts;
1039 /* first convert intended timestamp (in GstClockTime resolution) to
1040 * trak timescale, then derive delta;
1041 * this ensures sums of (scale)delta add up to converted timestamp,
1042 * which only deviates at most 1/scale from timestamp itself */
1043 scaled_duration = gst_util_uint64_scale (pad->last_dts + duration,
1044 atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts;
1045 pad->last_dts += duration;
1048 chunk_offset = qtmux->mdat_size;
1050 GST_LOG_OBJECT (qtmux,
1051 "Pad (%s) dts updated to %" GST_TIME_FORMAT,
1052 GST_PAD_NAME (pad->collect.pad), GST_TIME_ARGS (pad->last_dts));
1053 GST_LOG_OBJECT (qtmux,
1054 "Adding %d samples to track, duration: %" G_GUINT64_FORMAT
1055 " size: %" G_GUINT32_FORMAT " chunk offset: %" G_GUINT64_FORMAT,
1056 nsamples, scaled_duration, sample_size, chunk_offset);
1058 /* might be a sync sample */
1060 !GST_BUFFER_FLAG_IS_SET (last_buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
1061 GST_LOG_OBJECT (qtmux, "Adding new sync sample entry for track of pad %s",
1062 GST_PAD_NAME (pad->collect.pad));
1066 /* optionally calculate ctts entry values
1067 * (if composition-time expected different from decoding-time) */
1068 /* really not recommended:
1069 * - decoder typically takes care of dts/pts issues
1070 * - in case of out-of-order, dts may only be determined as above
1071 * (e.g. sum of duration), which may be totally different from
1072 * buffer timestamps in case of multiple segment, non-perfect streams
1073 * (and just perhaps maybe with some luck segment_to_running_time
1074 * or segment_to_media_time might get near to it) */
1075 if ((pad->have_dts || qtmux->guess_pts) && pad->is_out_of_order) {
1078 pts = gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (last_buf),
1079 atom_trak_get_timescale (pad->trak), GST_SECOND);
1080 pts_offset = (gint64) (pts - last_dts);
1082 GST_LOG_OBJECT (qtmux, "Adding ctts entry for pad %s: %" G_GINT64_FORMAT,
1083 GST_PAD_NAME (pad->collect.pad), pts_offset);
1086 /* now we go and register this buffer/sample all over */
1087 /* note that a new chunk is started each time (not fancy but works) */
1088 atom_trak_add_samples (pad->trak, nsamples, scaled_duration, sample_size,
1089 chunk_offset, sync, do_pts, pts_offset);
1092 gst_buffer_unref (buf);
1094 return gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE);
1100 gst_buffer_unref (buf);
1101 gst_buffer_unref (last_buf);
1102 return GST_FLOW_ERROR;
1106 GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1107 ("Failed to determine time to mux."));
1112 GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1113 ("Audio buffer contains fragmented sample."));
1118 GST_ELEMENT_ERROR (qtmux, CORE, NEGOTIATION, (NULL),
1119 ("format wasn't negotiated before buffer flow on pad %s",
1120 GST_PAD_NAME (pad->collect.pad)));
1121 gst_buffer_unref (buf);
1122 return GST_FLOW_NOT_NEGOTIATED;
1126 static GstFlowReturn
1127 gst_qt_mux_collected (GstCollectPads * pads, gpointer user_data)
1129 GstFlowReturn ret = GST_FLOW_OK;
1130 GstQTMux *qtmux = GST_QT_MUX_CAST (user_data);
1132 GstQTPad *best_pad = NULL;
1133 GstClockTime time, best_time = GST_CLOCK_TIME_NONE;
1136 if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_STARTED)) {
1137 if ((ret = gst_qt_mux_start_file (qtmux)) != GST_FLOW_OK)
1140 qtmux->state = GST_QT_MUX_STATE_DATA;
1143 if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS))
1144 return GST_FLOW_UNEXPECTED;
1146 /* select the best buffer */
1147 walk = qtmux->collect->data;
1150 GstCollectData *data;
1152 data = (GstCollectData *) walk->data;
1153 pad = (GstQTPad *) data;
1155 walk = g_slist_next (walk);
1157 buf = gst_collect_pads_peek (pads, data);
1159 GST_LOG_OBJECT (qtmux, "Pad %s has no buffers",
1160 GST_PAD_NAME (pad->collect.pad));
1163 time = GST_BUFFER_TIMESTAMP (buf);
1164 gst_buffer_unref (buf);
1166 if (best_pad == NULL || !GST_CLOCK_TIME_IS_VALID (time) ||
1167 (GST_CLOCK_TIME_IS_VALID (best_time) && time < best_time)) {
1173 if (best_pad != NULL) {
1174 GST_LOG_OBJECT (qtmux, "selected pad %s with time %" GST_TIME_FORMAT,
1175 GST_PAD_NAME (best_pad->collect.pad), GST_TIME_ARGS (best_time));
1176 buf = gst_collect_pads_pop (pads, &best_pad->collect);
1177 ret = gst_qt_mux_add_buffer (qtmux, best_pad, buf);
1179 ret = gst_qt_mux_stop_file (qtmux);
1180 if (ret == GST_FLOW_OK) {
1181 gst_pad_push_event (qtmux->srcpad, gst_event_new_eos ());
1182 ret = GST_FLOW_UNEXPECTED;
1184 qtmux->state = GST_QT_MUX_STATE_EOS;
1191 gst_qt_mux_audio_sink_set_caps (GstPad * pad, GstCaps * caps)
1193 GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1194 GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1195 GstQTPad *qtpad = NULL;
1196 GstStructure *structure;
1197 const gchar *mimetype;
1198 gint rate, channels;
1199 const GValue *value = NULL;
1200 const GstBuffer *codec_data = NULL;
1201 GstQTMuxFormat format;
1202 AudioSampleEntry entry = { 0, };
1203 AtomInfo *ext_atom = NULL;
1204 gint constant_size = 0;
1206 /* find stream data */
1207 qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
1210 /* does not go well to renegotiate stream mid-way */
1212 goto refuse_renegotiation;
1214 GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
1215 GST_DEBUG_PAD_NAME (pad), caps);
1217 format = qtmux_klass->format;
1218 structure = gst_caps_get_structure (caps, 0);
1219 mimetype = gst_structure_get_name (structure);
1222 if (!gst_structure_get_int (structure, "channels", &channels) ||
1223 !gst_structure_get_int (structure, "rate", &rate)) {
1228 value = gst_structure_get_value (structure, "codec_data");
1230 codec_data = gst_value_get_buffer (value);
1232 qtpad->is_out_of_order = FALSE;
1233 qtpad->have_dts = FALSE;
1235 /* set common properties */
1236 entry.sample_rate = rate;
1237 entry.channels = channels;
1239 entry.sample_size = 16;
1240 /* this is the typical compressed case */
1241 if (format == GST_QT_MUX_FORMAT_QT) {
1243 entry.compression_id = -2;
1246 /* now map onto a fourcc, and some extra properties */
1247 if (strcmp (mimetype, "audio/mpeg") == 0) {
1248 gint mpegversion = 0;
1251 gst_structure_get_int (structure, "mpegversion", &mpegversion);
1252 switch (mpegversion) {
1254 gst_structure_get_int (structure, "layer", &layer);
1258 /* note: QuickTime player does not like mp3 either way in iso/mp4 */
1259 if (format == GST_QT_MUX_FORMAT_QT)
1260 entry.fourcc = FOURCC__mp3;
1262 entry.fourcc = FOURCC_mp4a;
1264 build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG1_P3,
1265 ESDS_STREAM_TYPE_AUDIO, codec_data);
1267 entry.samples_per_packet = 1152;
1268 entry.bytes_per_sample = 2;
1274 entry.fourcc = FOURCC_mp4a;
1275 if (!codec_data || GST_BUFFER_SIZE (codec_data) < 2)
1276 GST_WARNING_OBJECT (qtmux, "no (valid) codec_data for AAC audio");
1278 guint8 profile = GST_READ_UINT8 (GST_BUFFER_DATA (codec_data));
1280 /* warn if not Low Complexity profile */
1283 GST_WARNING_OBJECT (qtmux,
1284 "non-LC AAC may not run well on (Apple) QuickTime/iTunes");
1286 if (format == GST_QT_MUX_FORMAT_QT)
1287 ext_atom = build_mov_aac_extension (qtpad->trak, codec_data);
1290 build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P3,
1291 ESDS_STREAM_TYPE_AUDIO, codec_data);
1296 } else if (strcmp (mimetype, "audio/AMR") == 0) {
1297 entry.fourcc = FOURCC_samr;
1298 entry.sample_size = 16;
1299 entry.samples_per_packet = 160;
1300 entry.bytes_per_sample = 2;
1301 ext_atom = build_amr_extension ();
1302 } else if (strcmp (mimetype, "audio/AMR-WB") == 0) {
1303 entry.fourcc = FOURCC_sawb;
1304 entry.sample_size = 16;
1305 entry.samples_per_packet = 320;
1306 entry.bytes_per_sample = 2;
1307 ext_atom = build_amr_extension ();
1308 } else if (strcmp (mimetype, "audio/x-raw-int") == 0) {
1314 if (!gst_structure_get_int (structure, "width", &width) ||
1315 !gst_structure_get_int (structure, "depth", &depth) ||
1316 !gst_structure_get_boolean (structure, "signed", &sign) ||
1317 !gst_structure_get_int (structure, "endianness", &endianness)) {
1318 GST_DEBUG_OBJECT (qtmux,
1319 "broken caps, width/depth/signed/endianness field missing");
1323 /* spec has no place for a distinction in these */
1324 if (width != depth) {
1325 GST_DEBUG_OBJECT (qtmux, "width must be same as depth!");
1330 if (endianness == G_LITTLE_ENDIAN)
1331 entry.fourcc = FOURCC_sowt;
1332 else if (endianness == G_BIG_ENDIAN)
1333 entry.fourcc = FOURCC_twos;
1334 /* maximum backward compatibility; only new version for > 16 bit */
1337 /* not compressed in any case */
1338 entry.compression_id = 0;
1339 /* QT spec says: max at 16 bit even if sample size were actually larger,
1340 * however, most players (e.g. QuickTime!) seem to disagree, so ... */
1341 entry.sample_size = depth;
1342 entry.bytes_per_sample = depth / 8;
1343 entry.samples_per_packet = 1;
1344 entry.bytes_per_packet = depth / 8;
1345 entry.bytes_per_frame = entry.bytes_per_packet * channels;
1347 if (width == 8 && depth == 8) {
1348 /* fall back to old 8-bit version */
1349 entry.fourcc = FOURCC_raw_;
1351 entry.compression_id = 0;
1352 entry.sample_size = 8;
1354 GST_DEBUG_OBJECT (qtmux, "non 8-bit PCM must be signed");
1358 constant_size = (depth / 8) * channels;
1359 } else if (strcmp (mimetype, "audio/x-alaw") == 0) {
1360 entry.fourcc = FOURCC_alaw;
1361 entry.samples_per_packet = 1023;
1362 entry.bytes_per_sample = 2;
1363 } else if (strcmp (mimetype, "audio/x-mulaw") == 0) {
1364 entry.fourcc = FOURCC_ulaw;
1365 entry.samples_per_packet = 1023;
1366 entry.bytes_per_sample = 2;
1372 /* ok, set the pad info accordingly */
1373 qtpad->fourcc = entry.fourcc;
1374 qtpad->sample_size = constant_size;
1375 atom_trak_set_audio_type (qtpad->trak, qtmux->context, &entry,
1376 entry.sample_rate, ext_atom, constant_size);
1378 gst_object_unref (qtmux);
1384 GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
1385 GST_PAD_NAME (pad), caps);
1386 gst_object_unref (qtmux);
1389 refuse_renegotiation:
1391 GST_WARNING_OBJECT (qtmux,
1392 "pad %s refused renegotiation to %" GST_PTR_FORMAT,
1393 GST_PAD_NAME (pad), caps);
1394 gst_object_unref (qtmux);
1399 /* scale rate up or down by factor of 10 to fit into [1000,10000] interval */
1401 adjust_rate (guint64 rate)
1403 while (rate >= 10000)
1409 return (guint32) rate;
1413 gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps)
1415 GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1416 GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1417 GstQTPad *qtpad = NULL;
1418 GstStructure *structure;
1419 const gchar *mimetype;
1420 gint width, height, depth = -1;
1421 gint framerate_num, framerate_den;
1423 const GValue *value = NULL;
1424 const GstBuffer *codec_data = NULL;
1425 VisualSampleEntry entry = { 0, };
1426 GstQTMuxFormat format;
1427 AtomInfo *ext_atom = NULL;
1428 gboolean sync = FALSE;
1429 int par_num, par_den;
1431 /* find stream data */
1432 qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
1435 /* does not go well to renegotiate stream mid-way */
1437 goto refuse_renegotiation;
1439 GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
1440 GST_DEBUG_PAD_NAME (pad), caps);
1442 format = qtmux_klass->format;
1443 structure = gst_caps_get_structure (caps, 0);
1444 mimetype = gst_structure_get_name (structure);
1446 /* required parts */
1447 if (!gst_structure_get_int (structure, "width", &width) ||
1448 !gst_structure_get_int (structure, "height", &height))
1453 /* works as a default timebase */
1454 framerate_num = 10000;
1456 gst_structure_get_fraction (structure, "framerate", &framerate_num,
1458 gst_structure_get_int (structure, "depth", &depth);
1459 value = gst_structure_get_value (structure, "codec_data");
1461 codec_data = gst_value_get_buffer (value);
1465 gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_num,
1467 /* FIXME: pixel-aspect-ratio */
1469 qtpad->is_out_of_order = FALSE;
1471 /* bring frame numerator into a range that ensures both reasonable resolution
1472 * as well as a fair duration */
1473 rate = adjust_rate (framerate_num);
1474 GST_DEBUG_OBJECT (qtmux, "Rate of video track selected: %" G_GUINT32_FORMAT,
1477 /* set common properties */
1478 entry.width = width;
1479 entry.height = height;
1480 /* should be OK according to qt and iso spec, override if really needed */
1481 entry.color_table_id = -1;
1482 entry.frame_count = 1;
1485 /* sync entries by default */
1488 /* now map onto a fourcc, and some extra properties */
1489 if (strcmp (mimetype, "video/x-raw-rgb") == 0) {
1492 entry.fourcc = FOURCC_raw_;
1493 gst_structure_get_int (structure, "bpp", &bpp);
1496 } else if (strcmp (mimetype, "video/x-raw-yuv") == 0) {
1500 gst_structure_get_fourcc (structure, "format", &format);
1502 case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
1505 entry.fourcc = FOURCC_2vuy;
1506 entry.depth = depth;
1509 } else if (strcmp (mimetype, "video/x-h263") == 0) {
1510 entry.fourcc = FOURCC_h263;
1511 ext_atom = build_h263_extension ();
1512 } else if (strcmp (mimetype, "video/x-divx") == 0 ||
1513 strcmp (mimetype, "video/mpeg") == 0) {
1516 if (strcmp (mimetype, "video/x-divx") == 0) {
1517 gst_structure_get_int (structure, "divxversion", &version);
1518 version = version == 5 ? 1 : 0;
1520 gst_structure_get_int (structure, "mpegversion", &version);
1521 version = version == 4 ? 1 : 0;
1524 entry.fourcc = FOURCC_mp4v;
1526 build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P2,
1527 ESDS_STREAM_TYPE_VISUAL, codec_data);
1529 GST_WARNING_OBJECT (qtmux, "no codec_data for MPEG4 video; "
1530 "output might not play in Apple QuickTime (try global-headers?)");
1532 } else if (strcmp (mimetype, "video/x-h264") == 0) {
1533 entry.fourcc = FOURCC_avc1;
1534 qtpad->is_out_of_order = TRUE;
1536 GST_WARNING_OBJECT (qtmux, "no codec_data in h264 caps");
1537 ext_atom = build_codec_data_extension (FOURCC_avcC, codec_data);
1538 } else if (strcmp (mimetype, "video/x-dv") == 0) {
1540 gboolean pal = TRUE;
1543 if (framerate_num != 25 || framerate_den != 1)
1545 gst_structure_get_int (structure, "dvversion", &version);
1546 /* fall back to typical one */
1552 entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', 'p');
1554 entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', ' ');
1558 entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'p');
1560 entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'n');
1563 GST_WARNING_OBJECT (qtmux, "unrecognized dv version");
1566 } else if (strcmp (mimetype, "image/jpeg") == 0) {
1567 entry.fourcc = FOURCC_jpeg;
1569 } else if (strcmp (mimetype, "image/x-j2c") == 0) {
1572 entry.fourcc = FOURCC_mjp2;
1574 if (!gst_structure_get_fourcc (structure, "fourcc", &fourcc) ||
1576 build_jp2h_extension (qtpad->trak, width, height, fourcc))) {
1577 GST_DEBUG_OBJECT (qtmux, "missing or invalid fourcc in jp2 caps");
1580 } else if (strcmp (mimetype, "video/x-qt-part") == 0) {
1583 gst_structure_get_fourcc (structure, "format", &fourcc);
1584 entry.fourcc = fourcc;
1585 qtpad->is_out_of_order = TRUE;
1586 qtpad->have_dts = TRUE;
1587 } else if (strcmp (mimetype, "video/x-mp4-part") == 0) {
1590 gst_structure_get_fourcc (structure, "format", &fourcc);
1591 entry.fourcc = fourcc;
1592 qtpad->is_out_of_order = TRUE;
1593 qtpad->have_dts = TRUE;
1599 /* ok, set the pad info accordingly */
1600 qtpad->fourcc = entry.fourcc;
1602 atom_trak_set_video_type (qtpad->trak, qtmux->context, &entry, rate,
1605 gst_object_unref (qtmux);
1611 GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
1612 GST_PAD_NAME (pad), caps);
1613 gst_object_unref (qtmux);
1616 refuse_renegotiation:
1618 GST_WARNING_OBJECT (qtmux,
1619 "pad %s refused renegotiation to %" GST_PTR_FORMAT " from %"
1620 GST_PTR_FORMAT, GST_PAD_NAME (pad), caps, GST_PAD_CAPS (pad));
1621 gst_object_unref (qtmux);
1627 gst_qt_mux_sink_event (GstPad * pad, GstEvent * event)
1632 qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1633 switch (GST_EVENT_TYPE (event)) {
1634 case GST_EVENT_TAG:{
1636 GstTagSetter *setter = GST_TAG_SETTER (qtmux);
1637 const GstTagMergeMode mode = gst_tag_setter_get_tag_merge_mode (setter);
1639 GST_DEBUG_OBJECT (qtmux, "received tag event");
1640 gst_event_parse_tag (event, &list);
1641 gst_tag_setter_merge_tags (setter, list, mode);
1648 ret = qtmux->collect_event (pad, event);
1649 gst_object_unref (qtmux);
1655 gst_qt_mux_release_pad (GstElement * element, GstPad * pad)
1657 GstQTMux *mux = GST_QT_MUX_CAST (element);
1659 /* let GstCollectPads complain if it is some unknown pad */
1660 if (gst_collect_pads_remove_pad (mux->collect, pad))
1661 gst_element_remove_pad (element, pad);
1665 gst_qt_mux_request_new_pad (GstElement * element,
1666 GstPadTemplate * templ, const gchar * name)
1668 GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
1669 GstQTMux *qtmux = GST_QT_MUX_CAST (element);
1670 GstQTPad *collect_pad;
1674 GST_DEBUG_OBJECT (qtmux, "Requested pad: %s", GST_STR_NULL (name));
1676 if (qtmux->state != GST_QT_MUX_STATE_NONE) {
1677 GST_WARNING_OBJECT (qtmux, "Not providing request pad after stream start.");
1681 if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
1683 } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
1686 GST_WARNING_OBJECT (qtmux, "This is not our template!");
1690 /* add pad to collections */
1691 newpad = gst_pad_new_from_template (templ, name);
1692 collect_pad = (GstQTPad *)
1693 gst_collect_pads_add_pad_full (qtmux->collect, newpad, sizeof (GstQTPad),
1694 (GstCollectDataDestroyNotify) (gst_qt_mux_pad_reset));
1696 gst_qt_mux_pad_reset (collect_pad);
1697 collect_pad->trak = atom_trak_new (qtmux->context);
1698 atom_moov_add_trak (qtmux->moov, collect_pad->trak);
1700 /* set up pad functions */
1702 gst_pad_set_setcaps_function (newpad,
1703 GST_DEBUG_FUNCPTR (gst_qt_mux_audio_sink_set_caps));
1705 gst_pad_set_setcaps_function (newpad,
1706 GST_DEBUG_FUNCPTR (gst_qt_mux_video_sink_set_caps));
1708 /* FIXME: hacked way to override/extend the event function of
1709 * GstCollectPads; because it sets its own event function giving the
1710 * element no access to events.
1712 qtmux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
1713 gst_pad_set_event_function (newpad,
1714 GST_DEBUG_FUNCPTR (gst_qt_mux_sink_event));
1716 gst_pad_set_active (newpad, TRUE);
1717 gst_element_add_pad (element, newpad);
1723 gst_qt_mux_get_property (GObject * object,
1724 guint prop_id, GValue * value, GParamSpec * pspec)
1726 GstQTMux *qtmux = GST_QT_MUX_CAST (object);
1728 GST_OBJECT_LOCK (qtmux);
1730 case PROP_LARGE_FILE:
1731 g_value_set_boolean (value, qtmux->large_file);
1733 case PROP_MOVIE_TIMESCALE:
1734 g_value_set_uint (value, qtmux->timescale);
1737 g_value_set_boolean (value, qtmux->guess_pts);
1739 case PROP_FAST_START:
1740 g_value_set_boolean (value, qtmux->fast_start);
1742 case PROP_FAST_START_TEMP_FILE:
1743 g_value_set_string (value, qtmux->fast_start_file_path);
1746 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1749 GST_OBJECT_UNLOCK (qtmux);
1753 gst_qt_mux_generate_fast_start_file_path (GstQTMux * qtmux)
1757 if (qtmux->fast_start_file_path) {
1758 g_free (qtmux->fast_start_file_path);
1759 qtmux->fast_start_file_path = NULL;
1762 tmp = g_strdup_printf ("%s%d", "qtmux", g_random_int ());
1763 qtmux->fast_start_file_path = g_build_filename (g_get_tmp_dir (), tmp, NULL);
1768 gst_qt_mux_set_property (GObject * object,
1769 guint prop_id, const GValue * value, GParamSpec * pspec)
1771 GstQTMux *qtmux = GST_QT_MUX_CAST (object);
1773 GST_OBJECT_LOCK (qtmux);
1775 case PROP_LARGE_FILE:
1776 qtmux->large_file = g_value_get_boolean (value);
1778 case PROP_MOVIE_TIMESCALE:
1779 qtmux->timescale = g_value_get_uint (value);
1782 qtmux->guess_pts = g_value_get_boolean (value);
1784 case PROP_FAST_START:
1785 qtmux->fast_start = g_value_get_boolean (value);
1787 case PROP_FAST_START_TEMP_FILE:
1788 if (qtmux->fast_start_file_path) {
1789 g_free (qtmux->fast_start_file_path);
1791 qtmux->fast_start_file_path = g_value_dup_string (value);
1792 /* NULL means to generate a random one */
1793 if (!qtmux->fast_start_file_path) {
1794 gst_qt_mux_generate_fast_start_file_path (qtmux);
1798 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1801 GST_OBJECT_UNLOCK (qtmux);
1804 static GstStateChangeReturn
1805 gst_qt_mux_change_state (GstElement * element, GstStateChange transition)
1807 GstStateChangeReturn ret;
1808 GstQTMux *qtmux = GST_QT_MUX_CAST (element);
1810 switch (transition) {
1811 case GST_STATE_CHANGE_NULL_TO_READY:
1813 case GST_STATE_CHANGE_READY_TO_PAUSED:
1814 gst_collect_pads_start (qtmux->collect);
1815 qtmux->state = GST_QT_MUX_STATE_STARTED;
1817 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1819 case GST_STATE_CHANGE_PAUSED_TO_READY:
1820 gst_collect_pads_stop (qtmux->collect);
1826 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1828 switch (transition) {
1829 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1831 case GST_STATE_CHANGE_PAUSED_TO_READY:
1832 gst_qt_mux_reset (qtmux, TRUE);
1834 case GST_STATE_CHANGE_READY_TO_NULL:
1845 gst_qt_mux_register (GstPlugin * plugin)
1847 GTypeInfo typeinfo = {
1848 sizeof (GstQTMuxClass),
1849 (GBaseInitFunc) gst_qt_mux_base_init,
1851 (GClassInitFunc) gst_qt_mux_class_init,
1856 (GInstanceInitFunc) gst_qt_mux_init,
1858 static const GInterfaceInfo tag_setter_info = {
1862 GstQTMuxFormat format;
1863 GstQTMuxClassParams *params;
1866 GST_LOG ("Registering muxers");
1869 GstQTMuxFormatProp *prop;
1871 prop = &gst_qt_mux_format_list[i];
1872 format = prop->format;
1873 if (format == GST_QT_MUX_FORMAT_NONE)
1876 /* create a cache for these properties */
1877 params = g_new0 (GstQTMuxClassParams, 1);
1878 params->prop = prop;
1879 params->src_caps = gst_static_caps_get (&prop->src_caps);
1880 params->video_sink_caps = gst_static_caps_get (&prop->video_sink_caps);
1881 params->audio_sink_caps = gst_static_caps_get (&prop->audio_sink_caps);
1883 /* create the type now */
1884 type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo,
1886 g_type_set_qdata (type, GST_QT_MUX_PARAMS_QDATA, (gpointer) params);
1887 g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
1889 if (!gst_element_register (plugin, prop->name, GST_RANK_PRIMARY, type))
1895 GST_LOG ("Finished registering muxers");
1901 gst_qt_mux_plugin_init (GstPlugin * plugin)
1903 GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
1905 return gst_qt_mux_register (plugin);
1908 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1911 "Quicktime Muxer plugin",
1912 gst_qt_mux_plugin_init, VERSION, "LGPL", "gsoc2008 package",
1913 "embedded.ufcg.edu.br")