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>
80
GST_DEBUG_CATEGORY_STATIC (gst_qt_mux_debug);
81
#define GST_CAT_DEFAULT gst_qt_mux_debug
83
/* QTMux signals and args */
98
PROP_FAST_START_TEMP_FILE
101
#define MDAT_ATOM_HEADER_SIZE 16
102
#define DEFAULT_LARGE_FILE FALSE
103
#define DEFAULT_MOVIE_TIMESCALE 600
104
#define DEFAULT_DO_CTTS FALSE
105
#define DEFAULT_FAST_START FALSE
106
#define DEFAULT_FAST_START_TEMP_FILE NULL
108
static void gst_qt_mux_finalize (GObject * object);
110
static GstStateChangeReturn gst_qt_mux_change_state (GstElement * element,
111
GstStateChange transition);
113
/* property functions */
114
static void gst_qt_mux_set_property (GObject * object,
115
guint prop_id, const GValue * value, GParamSpec * pspec);
116
static void gst_qt_mux_get_property (GObject * object,
117
guint prop_id, GValue * value, GParamSpec * pspec);
120
static GstPad *gst_qt_mux_request_new_pad (GstElement * element,
121
GstPadTemplate * templ, const gchar * name);
122
static void gst_qt_mux_release_pad (GstElement * element, GstPad * pad);
125
static gboolean gst_qt_mux_sink_event (GstPad * pad, GstEvent * event);
127
static GstFlowReturn gst_qt_mux_collected (GstCollectPads * pads,
129
static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
132
static GstElementClass *parent_class = NULL;
135
gst_qt_mux_base_init (gpointer g_class)
137
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
138
GstQTMuxClass *klass = (GstQTMuxClass *) g_class;
139
GstQTMuxClassParams *params;
140
GstElementDetails details;
141
GstPadTemplate *videosinktempl, *audiosinktempl, *srctempl;
144
(GstQTMuxClassParams *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (g_class),
145
GST_QT_MUX_PARAMS_QDATA);
146
g_assert (params != NULL);
148
/* construct the element details struct */
149
details.longname = g_strdup_printf ("%s Muxer", params->prop->long_name);
150
details.klass = g_strdup ("Codec/Muxer");
151
details.description =
152
g_strdup_printf ("Multiplex audio and video into a %s file",
153
params->prop->long_name);
154
details.author = "Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>";
155
gst_element_class_set_details (element_class, &details);
156
g_free (details.longname);
157
g_free (details.klass);
158
g_free (details.description);
161
srctempl = gst_pad_template_new ("src", GST_PAD_SRC,
162
GST_PAD_ALWAYS, params->src_caps);
163
gst_element_class_add_pad_template (element_class, srctempl);
165
if (params->audio_sink_caps) {
166
audiosinktempl = gst_pad_template_new ("audio_%d",
167
GST_PAD_SINK, GST_PAD_REQUEST, params->audio_sink_caps);
168
gst_element_class_add_pad_template (element_class, audiosinktempl);
171
if (params->video_sink_caps) {
172
videosinktempl = gst_pad_template_new ("video_%d",
173
GST_PAD_SINK, GST_PAD_REQUEST, params->video_sink_caps);
174
gst_element_class_add_pad_template (element_class, videosinktempl);
177
klass->format = params->prop->format;
181
gst_qt_mux_class_init (GstQTMuxClass * klass)
183
GObjectClass *gobject_class;
184
GstElementClass *gstelement_class;
186
gobject_class = (GObjectClass *) klass;
187
gstelement_class = (GstElementClass *) klass;
189
parent_class = g_type_class_peek_parent (klass);
191
gobject_class->finalize = gst_qt_mux_finalize;
192
gobject_class->get_property = gst_qt_mux_get_property;
193
gobject_class->set_property = gst_qt_mux_set_property;
195
g_object_class_install_property (gobject_class, PROP_LARGE_FILE,
196
g_param_spec_boolean ("large-file", "Support for large files",
197
"Uses 64bits to some fields instead of 32bits, "
198
"providing support for large files",
199
DEFAULT_LARGE_FILE, G_PARAM_READWRITE));
200
g_object_class_install_property (gobject_class, PROP_MOVIE_TIMESCALE,
201
g_param_spec_uint ("movie-timescale", "Movie timescale",
202
"Timescale to use in the movie (units per second)",
203
1, G_MAXUINT32, DEFAULT_MOVIE_TIMESCALE,
204
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
205
g_object_class_install_property (gobject_class, PROP_DO_CTTS,
206
g_param_spec_boolean ("presentation-time",
207
"Include presentation-time info",
208
"Calculate and include presentation/composition time (in addition to decoding time)"
209
" (use with caution)", DEFAULT_DO_CTTS,
210
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
211
g_object_class_install_property (gobject_class, PROP_FAST_START,
212
g_param_spec_boolean ("faststart", "Format file to faststart",
213
"If the file should be formated for faststart (headers first). ",
214
DEFAULT_FAST_START, G_PARAM_READWRITE));
215
g_object_class_install_property (gobject_class, PROP_FAST_START_TEMP_FILE,
216
g_param_spec_string ("faststart-file", "File to use for storing buffers",
217
"File that will be used temporarily to store data from the stream when "
218
"creating a faststart file. If null a filepath will be created automatically",
219
DEFAULT_FAST_START_TEMP_FILE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
221
gstelement_class->request_new_pad =
222
GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
223
gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qt_mux_change_state);
224
gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_release_pad);
228
gst_qt_mux_pad_reset (GstQTPad * qtpad)
231
qtpad->is_out_of_order = FALSE;
232
qtpad->have_dts = FALSE;
233
qtpad->sample_size = 0;
238
gst_buffer_replace (&qtpad->last_buf, NULL);
240
/* reference owned elsewhere */
245
* Takes GstQTMux back to its initial state
248
gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
252
qtmux->state = GST_QT_MUX_STATE_NONE;
253
qtmux->header_size = 0;
254
qtmux->mdat_size = 0;
258
atom_ftyp_free (qtmux->ftyp);
262
atom_moov_free (qtmux->moov);
265
if (qtmux->fast_start_file) {
266
fclose (qtmux->fast_start_file);
267
qtmux->fast_start_file = NULL;
269
gst_tag_setter_reset_tags (GST_TAG_SETTER (qtmux));
272
for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
273
GstQTPad *qtpad = (GstQTPad *) walk->data;
274
gst_qt_mux_pad_reset (qtpad);
276
/* hm, moov_free above yanked the traks away from us,
277
* so do not free, but do clear */
282
qtmux->moov = atom_moov_new (qtmux->context);
287
gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
289
GstElementClass *klass = GST_ELEMENT_CLASS (qtmux_klass);
290
GstPadTemplate *templ;
293
templ = gst_element_class_get_pad_template (klass, "src");
294
qtmux->srcpad = gst_pad_new_from_template (templ, "src");
295
caps = gst_caps_copy (gst_pad_get_pad_template_caps (qtmux->srcpad));
296
gst_pad_set_caps (qtmux->srcpad, caps);
297
gst_caps_unref (caps);
298
gst_pad_use_fixed_caps (qtmux->srcpad);
299
gst_element_add_pad (GST_ELEMENT (qtmux), qtmux->srcpad);
301
qtmux->collect = gst_collect_pads_new ();
302
gst_collect_pads_set_function (qtmux->collect,
303
(GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_qt_mux_collected), qtmux);
305
/* properties set to default upon construction */
307
/* always need this */
309
atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format));
311
/* internals to initial state */
312
gst_qt_mux_reset (qtmux, TRUE);
317
gst_qt_mux_finalize (GObject * object)
319
GstQTMux *qtmux = GST_QT_MUX_CAST (object);
321
gst_qt_mux_reset (qtmux, FALSE);
323
if (qtmux->fast_start_file_path)
324
g_free (qtmux->fast_start_file_path);
326
atoms_context_free (qtmux->context);
327
gst_object_unref (qtmux->collect);
329
G_OBJECT_CLASS (parent_class)->finalize (object);
332
/* FIXME approach below is pretty Apple/MOV/MP4/iTunes specific,
333
* and as such does not comply with e.g. 3GPP specs */
336
* Struct to record mappings from gstreamer tags to fourcc codes
338
typedef struct _GstTagToFourcc
342
const gchar *gsttag2;
345
/* tag list tags to fourcc matching */
346
static const GstTagToFourcc tag_matches[] = {
347
{FOURCC__alb, GST_TAG_ALBUM,},
348
{FOURCC__ART, GST_TAG_ARTIST,},
349
{FOURCC__cmt, GST_TAG_COMMENT,},
350
{FOURCC__wrt, GST_TAG_COMPOSER,},
351
{FOURCC__gen, GST_TAG_GENRE,},
352
{FOURCC__nam, GST_TAG_TITLE,},
353
{FOURCC__des, GST_TAG_DESCRIPTION,},
354
{FOURCC__too, GST_TAG_ENCODER,},
355
{FOURCC_cprt, GST_TAG_COPYRIGHT,},
356
{FOURCC_keyw, GST_TAG_KEYWORDS,},
357
{FOURCC__day, GST_TAG_DATE,},
358
{FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE,},
359
{FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT},
360
{FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT},
361
{FOURCC_covr, GST_TAG_PREVIEW_IMAGE,},
365
/* qtdemux produces these for atoms it cannot parse */
366
#define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag"
369
gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list)
373
const gchar *tag, *tag2;
375
for (i = 0; tag_matches[i].fourcc; i++) {
376
fourcc = tag_matches[i].fourcc;
377
tag = tag_matches[i].gsttag;
378
tag2 = tag_matches[i].gsttag2;
380
switch (gst_tag_get_type (tag)) {
386
if (!gst_tag_list_get_string (list, tag, &str) || !str)
388
GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
389
GST_FOURCC_ARGS (fourcc), str);
390
atom_moov_add_str_tag (qtmux->moov, fourcc, str);
399
if (!gst_tag_list_get_double (list, tag, &value))
401
GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
402
GST_FOURCC_ARGS (fourcc), (gint) value);
403
atom_moov_add_uint_tag (qtmux->moov, fourcc, 21, (gint) value);
406
/* paired unsigned integers */
412
if (!gst_tag_list_get_uint (list, tag, &value) ||
413
!gst_tag_list_get_uint (list, tag2, &count))
415
GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u",
416
GST_FOURCC_ARGS (fourcc), value, count);
417
atom_moov_add_uint_tag (qtmux->moov, fourcc, 0,
418
value << 16 | (count & 0xFFFF));
423
if (gst_tag_get_type (tag) == GST_TYPE_DATE) {
430
if (!gst_tag_list_get_date (list, tag, &date) || !date)
432
year = g_date_get_year (date);
433
month = g_date_get_month (date);
434
day = g_date_get_day (date);
436
if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH &&
437
day == G_DATE_BAD_DAY) {
438
GST_WARNING_OBJECT (qtmux, "invalid date in tag");
442
str = g_strdup_printf ("%u-%u-%u", year, month, day);
443
GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
444
GST_FOURCC_ARGS (fourcc), str);
445
atom_moov_add_str_tag (qtmux->moov, fourcc, str);
446
} else if (gst_tag_get_type (tag) == GST_TYPE_BUFFER) {
447
GValue value = { 0, };
450
GstStructure *structure;
453
if (!gst_tag_list_copy_value (&value, list, tag))
456
buf = gst_value_get_buffer (&value);
460
caps = gst_buffer_get_caps (buf);
462
GST_WARNING_OBJECT (qtmux, "preview image without caps");
466
GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);
468
structure = gst_caps_get_structure (caps, 0);
469
if (gst_structure_has_name (structure, "image/jpeg"))
471
else if (gst_structure_has_name (structure, "image/png"))
473
gst_caps_unref (caps);
476
GST_WARNING_OBJECT (qtmux, "preview image format not supported");
480
GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT
481
" -> image size %d", GST_FOURCC_ARGS (fourcc),
482
GST_BUFFER_SIZE (buf));
483
atom_moov_add_tag (qtmux->moov, fourcc, flags, GST_BUFFER_DATA (buf),
484
GST_BUFFER_SIZE (buf));
486
g_value_unset (&value);
488
g_assert_not_reached ();
494
/* add unparsed blobs if present */
495
if (gst_tag_exists (GST_QT_DEMUX_PRIVATE_TAG)) {
498
num_tags = gst_tag_list_get_tag_size (list, GST_QT_DEMUX_PRIVATE_TAG);
499
for (i = 0; i < num_tags; ++i) {
502
GstCaps *caps = NULL;
504
val = gst_tag_list_get_value_index (list, GST_QT_DEMUX_PRIVATE_TAG, i);
505
buf = (GstBuffer *) gst_value_get_mini_object (val);
507
if (buf && (caps = gst_buffer_get_caps (buf))) {
509
const gchar *style = NULL;
511
GST_DEBUG_OBJECT (qtmux, "Found private tag %d/%d; size %d, caps %"
512
GST_PTR_FORMAT, i, num_tags, GST_BUFFER_SIZE (buf), caps);
513
s = gst_caps_get_structure (caps, 0);
514
if (s && (style = gst_structure_get_string (s, "style"))) {
515
/* FIXME make into a parameter */
516
if (strcmp (style, "itunes") == 0) {
517
GST_DEBUG_OBJECT (qtmux, "Adding private tag");
518
atom_moov_add_blob_tag (qtmux->moov, GST_BUFFER_DATA (buf),
519
GST_BUFFER_SIZE (buf));
522
gst_caps_unref (caps);
531
* Gets the tagsetter iface taglist and puts the known tags
532
* into the output stream
535
gst_qt_mux_setup_metadata (GstQTMux * qtmux)
537
const GstTagList *tags;
539
tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (qtmux));
541
GST_LOG_OBJECT (qtmux, "tags: %" GST_PTR_FORMAT, tags);
543
if (tags && !gst_tag_list_is_empty (tags)) {
544
GST_DEBUG_OBJECT (qtmux, "Formatting tags");
545
gst_qt_mux_add_metadata_tags (qtmux, tags);
547
GST_DEBUG_OBJECT (qtmux, "No tags received");
552
gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset,
559
g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
561
data = GST_BUFFER_DATA (buf);
562
size = GST_BUFFER_SIZE (buf);
564
GST_LOG_OBJECT (qtmux, "sending buffer size %d", size);
566
if (mind_fast && qtmux->fast_start_file) {
569
GST_LOG_OBJECT (qtmux, "to temporary file");
570
ret = fwrite (data, sizeof (guint8), size, qtmux->fast_start_file);
571
gst_buffer_unref (buf);
577
GST_LOG_OBJECT (qtmux, "downstream");
579
gst_buffer_set_caps (buf, GST_PAD_CAPS (qtmux->srcpad));
580
res = gst_pad_push (qtmux->srcpad, buf);
591
GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
592
("Failed to write to temporary file"), GST_ERROR_SYSTEM);
593
return GST_FLOW_ERROR;
598
gst_qt_mux_send_buffered_data (GstQTMux * qtmux, guint64 * offset)
600
GstFlowReturn ret = GST_FLOW_OK;
601
GstBuffer *buf = NULL;
603
if (fflush (qtmux->fast_start_file))
606
if (fseek (qtmux->fast_start_file, 0, SEEK_SET))
609
/* hm, this could all take a really really long time,
610
* but there may not be another way to get moov atom first
611
* (somehow optimize copy?) */
612
GST_DEBUG_OBJECT (qtmux, "Sending buffered data");
613
while (ret == GST_FLOW_OK) {
615
const int bufsize = 4096;
617
buf = gst_buffer_new_and_alloc (bufsize);
618
r = fread (GST_BUFFER_DATA (buf), sizeof (guint8), bufsize,
619
qtmux->fast_start_file);
622
GST_BUFFER_SIZE (buf) = r;
623
GST_LOG_OBJECT (qtmux, "Pushing buffered buffer of size %d", r);
624
ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
628
gst_buffer_unref (buf);
631
/* best cleaning up effort, eat possible error */
632
fclose (qtmux->fast_start_file);
633
qtmux->fast_start_file = NULL;
635
/* FIXME maybe delete temporary file, or let the system handle that ? */
642
GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
643
("Failed to flush temporary file"), GST_ERROR_SYSTEM);
644
ret = GST_FLOW_ERROR;
649
GST_ELEMENT_ERROR (qtmux, RESOURCE, SEEK,
650
("Failed to seek temporary file"), GST_ERROR_SYSTEM);
651
ret = GST_FLOW_ERROR;
657
* Sends the initial mdat atom fields (size fields and fourcc type),
658
* the subsequent buffers are considered part of it's data.
659
* As we can't predict the amount of data that we are going to place in mdat
660
* we need to record the position of the size field in the stream so we can
661
* seek back to it later and update when the streams have finished.
664
gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size)
671
GST_DEBUG_OBJECT (qtmux, "Sending mdat's atom header, "
672
"size %" G_GUINT64_FORMAT, size);
674
node_header = g_malloc0 (sizeof (Atom));
675
node_header->type = FOURCC_mdat;
676
/* use extended size */
677
node_header->size = 1;
678
node_header->extended_size = 0;
680
node_header->extended_size = size;
683
if (atom_copy_data (node_header, &data, &size, &offset) == 0)
684
goto serialize_error;
686
buf = gst_buffer_new ();
687
GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
688
GST_BUFFER_SIZE (buf) = offset;
690
g_free (node_header);
692
GST_LOG_OBJECT (qtmux, "Pushing mdat start");
693
return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
698
GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
699
("Failed to serialize ftyp"));
700
return GST_FLOW_ERROR;
705
* We get the position of the mdat size field, seek back to it
706
* and overwrite with the real value
709
gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos,
710
guint64 mdat_size, guint64 * offset)
715
/* seek and rewrite the header */
716
event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES,
717
mdat_pos, GST_CLOCK_TIME_NONE, 0);
718
gst_pad_push_event (qtmux->srcpad, event);
720
buf = gst_buffer_new_and_alloc (sizeof (guint64));
721
GST_WRITE_UINT64_BE (GST_BUFFER_DATA (buf), mdat_size);
723
return gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
727
gst_qt_mux_stop_file (GstQTMux * qtmux)
729
gboolean ret = GST_FLOW_OK;
730
GstBuffer *buffer = NULL;
731
guint64 offset = 0, size = 0;
737
GST_DEBUG_OBJECT (qtmux, "Updating remaining values and sending last data");
739
/* pushing last buffers for each pad */
740
for (walk = qtmux->collect->data; walk; walk = g_slist_next (walk)) {
741
GstCollectData *cdata = (GstCollectData *) walk->data;
742
GstQTPad *qtpad = (GstQTPad *) cdata;
744
/* send last buffer */
745
GST_DEBUG_OBJECT (qtmux, "Sending the last buffer for pad %s",
746
GST_PAD_NAME (qtpad->collect.pad));
747
ret = gst_qt_mux_add_buffer (qtmux, qtpad, NULL);
748
if (ret != GST_FLOW_OK)
749
GST_DEBUG_OBJECT (qtmux, "Failed to send last buffer for %s, "
750
"flow return: %s", GST_PAD_NAME (qtpad->collect.pad),
751
gst_flow_get_name (ret));
754
GST_OBJECT_LOCK (qtmux);
755
timescale = qtmux->timescale;
756
large_file = qtmux->large_file;
757
GST_OBJECT_UNLOCK (qtmux);
759
/* inform lower layers of our property wishes, and determine duration.
760
* Let moov take care of this using its list of traks;
761
* so that released pads are also included */
762
GST_DEBUG_OBJECT (qtmux, "Large file support: %d", large_file);
763
GST_DEBUG_OBJECT (qtmux, "Updating timescale to %" G_GUINT32_FORMAT,
765
atom_moov_update_timescale (qtmux->moov, timescale);
766
atom_moov_set_64bits (qtmux->moov, large_file);
767
atom_moov_update_duration (qtmux->moov);
769
/* tags into file metadata */
770
gst_qt_mux_setup_metadata (qtmux);
772
/* if faststart, update the offset of the atoms in the movie with the offset
773
* that the movie headers before mdat will cause */
774
if (qtmux->fast_start_file) {
775
/* copy into NULL to obtain size */
777
if (!atom_moov_copy_data (qtmux->moov, NULL, &size, &offset))
778
goto serialize_error;
779
GST_DEBUG_OBJECT (qtmux, "calculated moov atom size %" G_GUINT64_FORMAT,
781
offset += qtmux->header_size + MDAT_ATOM_HEADER_SIZE;
783
offset = qtmux->header_size;
784
atom_moov_chunks_add_offset (qtmux->moov, offset);
789
GST_LOG_OBJECT (qtmux, "Copying movie header into buffer");
790
ret = atom_moov_copy_data (qtmux->moov, &data, &size, &offset);
792
goto serialize_error;
794
buffer = gst_buffer_new ();
795
GST_BUFFER_DATA (buffer) = GST_BUFFER_MALLOCDATA (buffer) = data;
796
GST_BUFFER_SIZE (buffer) = offset;
797
/* note: as of this point, we no longer care about tracking written data size,
798
* since there is no more use for it anyway */
799
GST_DEBUG_OBJECT (qtmux, "Pushing movie atoms");
800
gst_qt_mux_send_buffer (qtmux, buffer, NULL, FALSE);
802
/* total mdat size as of now also includes the atom header */
803
qtmux->mdat_size += MDAT_ATOM_HEADER_SIZE;
804
/* if needed, send mdat atom and move buffered data into it */
805
if (qtmux->fast_start_file) {
806
/* mdat size = accumulated (buffered data) + mdat atom header */
807
ret = gst_qt_mux_send_mdat_header (qtmux, NULL, qtmux->mdat_size);
808
if (ret != GST_FLOW_OK)
810
ret = gst_qt_mux_send_buffered_data (qtmux, NULL);
811
if (ret != GST_FLOW_OK)
814
/* mdata needs update iff not using faststart */
815
GST_DEBUG_OBJECT (qtmux, "updating mdata size");
816
ret = gst_qt_mux_update_mdat_size (qtmux, qtmux->mdat_pos,
817
qtmux->mdat_size, NULL);
818
/* note; no seeking back to the end of file is done,
819
* since we longer write anything anyway */
827
gst_buffer_unref (buffer);
828
GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
829
("Failed to serialize moov"));
830
return GST_FLOW_ERROR;
835
gst_qt_mux_send_ftyp (GstQTMux * qtmux, guint64 * off)
838
guint64 size = 0, offset = 0;
841
GST_DEBUG_OBJECT (qtmux, "Sending ftyp atom");
843
if (!atom_ftyp_copy_data (qtmux->ftyp, &data, &size, &offset))
844
goto serialize_error;
846
buf = gst_buffer_new ();
847
GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
848
GST_BUFFER_SIZE (buf) = offset;
850
GST_LOG_OBJECT (qtmux, "Pushing ftyp");
851
return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
856
GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
857
("Failed to serialize ftyp"));
858
return GST_FLOW_ERROR;
863
gst_qt_mux_start_file (GstQTMux * qtmux)
865
GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
866
GstFlowReturn ret = GST_FLOW_OK;
867
guint32 major, version;
871
GST_DEBUG_OBJECT (qtmux, "starting file");
873
/* let downstream know we think in BYTES and expect to do seeking later on */
874
gst_pad_push_event (qtmux->srcpad,
875
gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0));
877
/* init and send context and ftyp based on current property state */
879
atom_ftyp_free (qtmux->ftyp);
880
gst_qt_mux_map_format_to_header (qtmux_klass->format, &prefix, &major,
881
&version, &comp, qtmux->moov);
882
qtmux->ftyp = atom_ftyp_new (qtmux->context, major, version, comp);
886
ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE);
887
if (ret != GST_FLOW_OK)
890
ret = gst_qt_mux_send_ftyp (qtmux, &qtmux->header_size);
891
if (ret != GST_FLOW_OK)
894
/* send mdat header if already needed, and mark position for later update */
895
GST_OBJECT_LOCK (qtmux);
896
if (qtmux->fast_start) {
897
qtmux->fast_start_file = g_fopen (qtmux->fast_start_file_path, "wb+");
898
if (!qtmux->fast_start_file)
901
ret = gst_qt_mux_send_mdat_header (qtmux, &qtmux->header_size, 0);
902
/* mdat size position = current header pos - extended header size */
903
qtmux->mdat_pos = qtmux->header_size - sizeof (guint64);
905
GST_OBJECT_UNLOCK (qtmux);
913
GST_ELEMENT_ERROR (qtmux, RESOURCE, OPEN_READ_WRITE,
914
(("Could not open temporary file \"%s\""), qtmux->fast_start_file_path),
916
GST_OBJECT_UNLOCK (qtmux);
917
return GST_FLOW_ERROR;
922
* Here we push the buffer and update the tables in the track atoms
925
gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
927
GstBuffer *last_buf = NULL;
928
GstClockTime duration;
929
guint nsamples, sample_size;
930
guint64 scaled_duration, chunk_offset;
932
gint64 pts_offset = 0;
933
gboolean sync = FALSE, do_pts = FALSE;
938
last_buf = pad->last_buf;
939
if (last_buf == NULL) {
940
#ifndef GST_DISABLE_GST_DEBUG
942
GST_DEBUG_OBJECT (qtmux, "Pad %s has no previous buffer stored and "
943
"received NULL buffer, doing nothing",
944
GST_PAD_NAME (pad->collect.pad));
946
GST_LOG_OBJECT (qtmux,
947
"Pad %s has no previous buffer stored, storing now",
948
GST_PAD_NAME (pad->collect.pad));
954
gst_buffer_ref (last_buf);
956
/* fall back to duration if:
958
* - this format has out of order buffers (e.g. MPEG-4),
959
* - lack of valid time forces fall back */
960
if (buf == NULL || pad->is_out_of_order ||
961
!GST_BUFFER_TIMESTAMP_IS_VALID (last_buf) ||
962
!GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
963
if (!GST_BUFFER_DURATION_IS_VALID (last_buf)) {
964
/* be forgiving for some possibly last upstream flushed buffer */
967
GST_WARNING_OBJECT (qtmux, "no duration for last buffer");
968
/* iso spec recommends some small value, try 0 */
971
duration = GST_BUFFER_DURATION (last_buf);
974
duration = GST_BUFFER_TIMESTAMP (buf) - GST_BUFFER_TIMESTAMP (last_buf);
977
gst_buffer_replace (&pad->last_buf, buf);
979
last_dts = gst_util_uint64_scale (pad->last_dts,
980
atom_trak_get_timescale (pad->trak), GST_SECOND);
982
/* raw audio has many samples per buffer (= chunk) */
983
if (pad->sample_size) {
984
sample_size = pad->sample_size;
985
if (GST_BUFFER_SIZE (last_buf) % sample_size != 0)
986
goto fragmented_sample;
987
/* note: qt raw audio storage warps it implicitly into a timewise
988
* perfect stream, discarding buffer times */
989
nsamples = GST_BUFFER_SIZE (last_buf) / sample_size;
990
duration = GST_BUFFER_DURATION (last_buf) / nsamples;
991
/* timescale = samplerate */
993
pad->last_dts += duration * nsamples;
996
sample_size = GST_BUFFER_SIZE (last_buf);
999
pad->last_dts = GST_BUFFER_OFFSET_END (last_buf);
1000
if ((gint64) (pad->last_dts) < 0) {
1001
scaled_dts = -gst_util_uint64_scale (-pad->last_dts,
1002
atom_trak_get_timescale (pad->trak), GST_SECOND);
1004
scaled_dts = gst_util_uint64_scale (pad->last_dts,
1005
atom_trak_get_timescale (pad->trak), GST_SECOND);
1007
scaled_duration = scaled_dts - last_dts;
1008
last_dts = scaled_dts;
1010
/* first convert intended timestamp (in GstClockTime resolution) to
1011
* trak timescale, then derive delta;
1012
* this ensures sums of (scale)delta add up to converted timestamp,
1013
* which only deviates at most 1/scale from timestamp itself */
1014
scaled_duration = gst_util_uint64_scale (pad->last_dts + duration,
1015
atom_trak_get_timescale (pad->trak), GST_SECOND) - last_dts;
1016
pad->last_dts += duration;
1019
chunk_offset = qtmux->mdat_size;
1021
GST_LOG_OBJECT (qtmux,
1022
"Pad (%s) dts updated to %" GST_TIME_FORMAT,
1023
GST_PAD_NAME (pad->collect.pad), GST_TIME_ARGS (pad->last_dts));
1024
GST_LOG_OBJECT (qtmux,
1025
"Adding %d samples to track, duration: %" G_GUINT64_FORMAT
1026
" size: %" G_GUINT32_FORMAT " chunk offset: %" G_GUINT64_FORMAT,
1027
nsamples, scaled_duration, sample_size, chunk_offset);
1029
/* might be a sync sample */
1031
!GST_BUFFER_FLAG_IS_SET (last_buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
1032
GST_LOG_OBJECT (qtmux, "Adding new sync sample entry for track of pad %s",
1033
GST_PAD_NAME (pad->collect.pad));
1037
/* optionally calculate ctts entry values
1038
* (if composition-time expected different from decoding-time) */
1039
/* really not recommended:
1040
* - decoder typically takes care of dts/pts issues
1041
* - in case of out-of-order, dts may only be determined as above
1042
* (e.g. sum of duration), which may be totally different from
1043
* buffer timestamps in case of multiple segment, non-perfect streams
1044
* (and just perhaps maybe with some luck segment_to_running_time
1045
* or segment_to_media_time might get near to it) */
1046
if ((pad->have_dts || qtmux->guess_pts) && pad->is_out_of_order) {
1049
pts = gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (last_buf),
1050
atom_trak_get_timescale (pad->trak), GST_SECOND);
1051
pts_offset = (gint64) (pts - last_dts);
1053
GST_LOG_OBJECT (qtmux, "Adding ctts entry for pad %s: %" G_GINT64_FORMAT,
1054
GST_PAD_NAME (pad->collect.pad), pts_offset);
1057
/* now we go and register this buffer/sample all over */
1058
/* note that a new chunk is started each time (not fancy but works) */
1059
atom_trak_add_samples (pad->trak, nsamples, scaled_duration, sample_size,
1060
chunk_offset, sync, do_pts, pts_offset);
1063
gst_buffer_unref (buf);
1065
return gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE);
1071
gst_buffer_unref (buf);
1072
gst_buffer_unref (last_buf);
1073
return GST_FLOW_ERROR;
1077
GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1078
("Failed to determine time to mux."));
1083
GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
1084
("Audio buffer contains fragmented sample."));
1089
GST_ELEMENT_ERROR (qtmux, CORE, NEGOTIATION, (NULL),
1090
("format wasn't negotiated before buffer flow on pad %s",
1091
GST_PAD_NAME (pad->collect.pad)));
1092
gst_buffer_unref (buf);
1093
return GST_FLOW_NOT_NEGOTIATED;
1097
static GstFlowReturn
1098
gst_qt_mux_collected (GstCollectPads * pads, gpointer user_data)
1100
GstFlowReturn ret = GST_FLOW_OK;
1101
GstQTMux *qtmux = GST_QT_MUX_CAST (user_data);
1103
GstQTPad *best_pad = NULL;
1104
GstClockTime time, best_time = GST_CLOCK_TIME_NONE;
1107
if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_STARTED)) {
1108
if ((ret = gst_qt_mux_start_file (qtmux)) != GST_FLOW_OK)
1111
qtmux->state = GST_QT_MUX_STATE_DATA;
1114
if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS))
1115
return GST_FLOW_UNEXPECTED;
1117
/* select the best buffer */
1118
walk = qtmux->collect->data;
1121
GstCollectData *data;
1123
data = (GstCollectData *) walk->data;
1124
pad = (GstQTPad *) data;
1126
walk = g_slist_next (walk);
1128
buf = gst_collect_pads_peek (pads, data);
1130
GST_LOG_OBJECT (qtmux, "Pad %s has no buffers",
1131
GST_PAD_NAME (pad->collect.pad));
1134
time = GST_BUFFER_TIMESTAMP (buf);
1135
gst_buffer_unref (buf);
1137
if (best_pad == NULL || !GST_CLOCK_TIME_IS_VALID (time) ||
1138
(GST_CLOCK_TIME_IS_VALID (best_time) && time < best_time)) {
1144
if (best_pad != NULL) {
1145
GST_LOG_OBJECT (qtmux, "selected pad %s with time %" GST_TIME_FORMAT,
1146
GST_PAD_NAME (best_pad->collect.pad), GST_TIME_ARGS (best_time));
1147
buf = gst_collect_pads_pop (pads, &best_pad->collect);
1148
ret = gst_qt_mux_add_buffer (qtmux, best_pad, buf);
1150
ret = gst_qt_mux_stop_file (qtmux);
1151
if (ret == GST_FLOW_OK) {
1152
gst_pad_push_event (qtmux->srcpad, gst_event_new_eos ());
1153
ret = GST_FLOW_UNEXPECTED;
1155
qtmux->state = GST_QT_MUX_STATE_EOS;
1162
gst_qt_mux_audio_sink_set_caps (GstPad * pad, GstCaps * caps)
1164
GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1165
GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1166
GstQTPad *qtpad = NULL;
1167
GstStructure *structure;
1168
const gchar *mimetype;
1169
gint rate, channels;
1170
const GValue *value = NULL;
1171
const GstBuffer *codec_data = NULL;
1172
GstQTMuxFormat format;
1173
AudioSampleEntry entry = { 0, };
1174
AtomInfo *ext_atom = NULL;
1175
gint constant_size = 0;
1177
/* find stream data */
1178
qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
1181
/* does not go well to renegotiate stream mid-way */
1183
goto refuse_renegotiation;
1185
GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
1186
GST_DEBUG_PAD_NAME (pad), caps);
1188
format = qtmux_klass->format;
1189
structure = gst_caps_get_structure (caps, 0);
1190
mimetype = gst_structure_get_name (structure);
1193
if (!gst_structure_get_int (structure, "channels", &channels) ||
1194
!gst_structure_get_int (structure, "rate", &rate)) {
1199
value = gst_structure_get_value (structure, "codec_data");
1201
codec_data = gst_value_get_buffer (value);
1203
qtpad->is_out_of_order = FALSE;
1204
qtpad->have_dts = FALSE;
1206
/* set common properties */
1207
entry.sample_rate = rate;
1208
entry.channels = channels;
1210
entry.sample_size = 16;
1211
/* this is the typical compressed case */
1212
if (format == GST_QT_MUX_FORMAT_QT) {
1214
entry.compression_id = -2;
1217
/* now map onto a fourcc, and some extra properties */
1218
if (strcmp (mimetype, "audio/mpeg") == 0) {
1219
gint mpegversion = 0;
1222
gst_structure_get_int (structure, "mpegversion", &mpegversion);
1223
switch (mpegversion) {
1225
gst_structure_get_int (structure, "layer", &layer);
1229
/* note: QuickTime player does not like mp3 either way in iso/mp4 */
1230
if (format == GST_QT_MUX_FORMAT_QT)
1231
entry.fourcc = FOURCC__mp3;
1233
entry.fourcc = FOURCC_mp4a;
1235
build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG1_P3,
1236
ESDS_STREAM_TYPE_AUDIO, codec_data);
1238
entry.samples_per_packet = 1152;
1239
entry.bytes_per_sample = 2;
1245
entry.fourcc = FOURCC_mp4a;
1246
if (!codec_data || GST_BUFFER_SIZE (codec_data) < 2)
1247
GST_WARNING_OBJECT (qtmux, "no (valid) codec_data for AAC audio");
1249
guint8 profile = GST_READ_UINT8 (GST_BUFFER_DATA (codec_data));
1251
/* warn if not Low Complexity profile */
1254
GST_WARNING_OBJECT (qtmux,
1255
"non-LC AAC may not run well on (Apple) QuickTime/iTunes");
1257
if (format == GST_QT_MUX_FORMAT_QT)
1258
ext_atom = build_mov_aac_extension (qtpad->trak, codec_data);
1261
build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P3,
1262
ESDS_STREAM_TYPE_AUDIO, codec_data);
1267
} else if (strcmp (mimetype, "audio/AMR") == 0) {
1268
entry.fourcc = FOURCC_samr;
1269
entry.sample_size = 16;
1270
entry.samples_per_packet = 160;
1271
entry.bytes_per_sample = 2;
1272
} else if (strcmp (mimetype, "audio/x-raw-int") == 0) {
1278
if (!gst_structure_get_int (structure, "width", &width) ||
1279
!gst_structure_get_int (structure, "depth", &depth) ||
1280
!gst_structure_get_boolean (structure, "signed", &sign) ||
1281
!gst_structure_get_int (structure, "endianness", &endianness)) {
1282
GST_DEBUG_OBJECT (qtmux,
1283
"broken caps, width/depth/signed/endianness field missing");
1287
/* spec has no place for a distinction in these */
1288
if (width != depth) {
1289
GST_DEBUG_OBJECT (qtmux, "width must be same as depth!");
1294
if (endianness == G_LITTLE_ENDIAN)
1295
entry.fourcc = FOURCC_sowt;
1296
else if (endianness == G_BIG_ENDIAN)
1297
entry.fourcc = FOURCC_twos;
1298
/* maximum backward compatibility; only new version for > 16 bit */
1301
/* not compressed in any case */
1302
entry.compression_id = 0;
1303
/* QT spec says: max at 16 bit even if sample size were actually larger,
1304
* however, most players (e.g. QuickTime!) seem to disagree, so ... */
1305
entry.sample_size = depth;
1306
entry.bytes_per_sample = depth / 8;
1307
entry.samples_per_packet = 1;
1308
entry.bytes_per_packet = depth / 8;
1309
entry.bytes_per_frame = entry.bytes_per_packet * channels;
1311
if (width == 8 && depth == 8) {
1312
/* fall back to old 8-bit version */
1313
entry.fourcc = FOURCC_raw_;
1315
entry.compression_id = 0;
1316
entry.sample_size = 8;
1318
GST_DEBUG_OBJECT (qtmux, "non 8-bit PCM must be signed");
1322
constant_size = (depth / 8) * channels;
1323
} else if (strcmp (mimetype, "audio/x-alaw") == 0) {
1324
entry.fourcc = FOURCC_alaw;
1325
entry.samples_per_packet = 1023;
1326
entry.bytes_per_sample = 2;
1327
} else if (strcmp (mimetype, "audio/x-mulaw") == 0) {
1328
entry.fourcc = FOURCC_ulaw;
1329
entry.samples_per_packet = 1023;
1330
entry.bytes_per_sample = 2;
1336
/* ok, set the pad info accordingly */
1337
qtpad->fourcc = entry.fourcc;
1338
qtpad->sample_size = constant_size;
1339
atom_trak_set_audio_type (qtpad->trak, qtmux->context, &entry,
1340
entry.sample_rate, ext_atom, constant_size);
1342
gst_object_unref (qtmux);
1348
GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
1349
GST_PAD_NAME (pad), caps);
1350
gst_object_unref (qtmux);
1353
refuse_renegotiation:
1355
GST_WARNING_OBJECT (qtmux,
1356
"pad %s refused renegotiation to %" GST_PTR_FORMAT,
1357
GST_PAD_NAME (pad), caps);
1358
gst_object_unref (qtmux);
1363
/* scale rate up or down by factor of 10 to fit into [1000,10000] interval */
1365
adjust_rate (guint64 rate)
1367
while (rate >= 10000)
1373
return (guint32) rate;
1377
gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps)
1379
GstQTMux *qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1380
GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1381
GstQTPad *qtpad = NULL;
1382
GstStructure *structure;
1383
const gchar *mimetype;
1384
gint width, height, depth = -1;
1385
gint framerate_num, framerate_den;
1387
const GValue *value = NULL;
1388
const GstBuffer *codec_data = NULL;
1389
VisualSampleEntry entry = { 0, };
1390
GstQTMuxFormat format;
1391
AtomInfo *ext_atom = NULL;
1392
gboolean sync = FALSE;
1393
int par_num, par_den;
1395
/* find stream data */
1396
qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
1399
/* does not go well to renegotiate stream mid-way */
1401
goto refuse_renegotiation;
1403
GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
1404
GST_DEBUG_PAD_NAME (pad), caps);
1406
format = qtmux_klass->format;
1407
structure = gst_caps_get_structure (caps, 0);
1408
mimetype = gst_structure_get_name (structure);
1410
/* required parts */
1411
if (!gst_structure_get_int (structure, "width", &width) ||
1412
!gst_structure_get_int (structure, "height", &height))
1417
/* works as a default timebase */
1418
framerate_num = 10000;
1420
gst_structure_get_fraction (structure, "framerate", &framerate_num,
1422
gst_structure_get_int (structure, "depth", &depth);
1423
value = gst_structure_get_value (structure, "codec_data");
1425
codec_data = gst_value_get_buffer (value);
1429
gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_num,
1431
/* FIXME: pixel-aspect-ratio */
1433
qtpad->is_out_of_order = FALSE;
1435
/* bring frame numerator into a range that ensures both reasonable resolution
1436
* as well as a fair duration */
1437
rate = adjust_rate (framerate_num);
1438
GST_DEBUG_OBJECT (qtmux, "Rate of video track selected: %" G_GUINT32_FORMAT,
1441
/* set common properties */
1442
entry.width = width;
1443
entry.height = height;
1444
/* should be OK according to qt and iso spec, override if really needed */
1445
entry.color_table_id = -1;
1446
entry.frame_count = 1;
1449
/* sync entries by default */
1452
/* now map onto a fourcc, and some extra properties */
1453
if (strcmp (mimetype, "video/x-raw-rgb") == 0) {
1456
entry.fourcc = FOURCC_raw_;
1457
gst_structure_get_int (structure, "bpp", &bpp);
1460
} else if (strcmp (mimetype, "video/x-raw-yuv") == 0) {
1464
gst_structure_get_fourcc (structure, "format", &format);
1466
case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
1469
entry.fourcc = FOURCC_2vuy;
1470
entry.depth = depth;
1473
} else if (strcmp (mimetype, "video/x-h263") == 0) {
1474
entry.fourcc = FOURCC_h263;
1475
} else if (strcmp (mimetype, "video/x-divx") == 0 ||
1476
strcmp (mimetype, "video/mpeg") == 0) {
1479
if (strcmp (mimetype, "video/x-divx") == 0) {
1480
gst_structure_get_int (structure, "divxversion", &version);
1481
version = version == 5 ? 1 : 0;
1483
gst_structure_get_int (structure, "mpegversion", &version);
1484
version = version == 4 ? 1 : 0;
1487
entry.fourcc = FOURCC_mp4v;
1489
build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P2,
1490
ESDS_STREAM_TYPE_VISUAL, codec_data);
1492
GST_WARNING_OBJECT (qtmux, "no codec_data for MPEG4 video; "
1493
"output might not play in Apple QuickTime (try global-headers?)");
1495
} else if (strcmp (mimetype, "video/x-h264") == 0) {
1496
entry.fourcc = FOURCC_avc1;
1497
qtpad->is_out_of_order = TRUE;
1499
GST_WARNING_OBJECT (qtmux, "no codec_data in h264 caps");
1500
ext_atom = build_codec_data_extension (FOURCC_avcC, codec_data);
1501
} else if (strcmp (mimetype, "video/x-dv") == 0) {
1503
gboolean pal = TRUE;
1506
if (framerate_num != 25 || framerate_den != 1)
1508
gst_structure_get_int (structure, "dvversion", &version);
1509
/* fall back to typical one */
1515
entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', 'p');
1517
entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', ' ');
1521
entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'p');
1523
entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'n');
1526
GST_WARNING_OBJECT (qtmux, "unrecognized dv version");
1529
} else if (strcmp (mimetype, "image/jpeg") == 0) {
1530
entry.fourcc = FOURCC_jpeg;
1532
} else if (strcmp (mimetype, "image/x-j2c") == 0) {
1535
entry.fourcc = FOURCC_mjp2;
1537
if (!gst_structure_get_fourcc (structure, "fourcc", &fourcc) ||
1539
build_jp2h_extension (qtpad->trak, width, height, fourcc))) {
1540
GST_DEBUG_OBJECT (qtmux, "missing or invalid fourcc in jp2 caps");
1543
} else if (strcmp (mimetype, "video/x-qt-part") == 0) {
1546
gst_structure_get_fourcc (structure, "format", &fourcc);
1547
entry.fourcc = fourcc;
1548
qtpad->is_out_of_order = TRUE;
1549
qtpad->have_dts = TRUE;
1550
} else if (strcmp (mimetype, "video/x-mp4-part") == 0) {
1553
gst_structure_get_fourcc (structure, "format", &fourcc);
1554
entry.fourcc = fourcc;
1555
qtpad->is_out_of_order = TRUE;
1556
qtpad->have_dts = TRUE;
1562
/* ok, set the pad info accordingly */
1563
qtpad->fourcc = entry.fourcc;
1565
atom_trak_set_video_type (qtpad->trak, qtmux->context, &entry, rate,
1568
gst_object_unref (qtmux);
1574
GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
1575
GST_PAD_NAME (pad), caps);
1576
gst_object_unref (qtmux);
1579
refuse_renegotiation:
1581
GST_WARNING_OBJECT (qtmux,
1582
"pad %s refused renegotiation to %" GST_PTR_FORMAT,
1583
GST_PAD_NAME (pad), caps);
1584
gst_object_unref (qtmux);
1590
gst_qt_mux_sink_event (GstPad * pad, GstEvent * event)
1595
qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
1596
switch (GST_EVENT_TYPE (event)) {
1597
case GST_EVENT_TAG:{
1599
GstTagSetter *setter = GST_TAG_SETTER (qtmux);
1600
const GstTagMergeMode mode = gst_tag_setter_get_tag_merge_mode (setter);
1602
GST_DEBUG_OBJECT (qtmux, "received tag event");
1603
gst_event_parse_tag (event, &list);
1604
gst_tag_setter_merge_tags (setter, list, mode);
1611
ret = qtmux->collect_event (pad, event);
1612
gst_object_unref (qtmux);
1618
gst_qt_mux_release_pad (GstElement * element, GstPad * pad)
1620
GstQTMux *mux = GST_QT_MUX_CAST (element);
1622
/* let GstCollectPads complain if it is some unknown pad */
1623
if (gst_collect_pads_remove_pad (mux->collect, pad))
1624
gst_element_remove_pad (element, pad);
1628
gst_qt_mux_request_new_pad (GstElement * element,
1629
GstPadTemplate * templ, const gchar * name)
1631
GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
1632
GstQTMux *qtmux = GST_QT_MUX_CAST (element);
1633
GstQTPad *collect_pad;
1637
GST_DEBUG_OBJECT (qtmux, "Requested pad: %s", GST_STR_NULL (name));
1639
if (qtmux->state != GST_QT_MUX_STATE_NONE) {
1640
GST_WARNING_OBJECT (qtmux, "Not providing request pad after stream start.");
1644
if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
1646
} else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
1649
GST_WARNING_OBJECT (qtmux, "This is not our template!");
1653
/* add pad to collections */
1654
newpad = gst_pad_new_from_template (templ, name);
1655
collect_pad = (GstQTPad *)
1656
gst_collect_pads_add_pad_full (qtmux->collect, newpad, sizeof (GstQTPad),
1657
(GstCollectDataDestroyNotify) (gst_qt_mux_pad_reset));
1659
gst_qt_mux_pad_reset (collect_pad);
1660
collect_pad->trak = atom_trak_new (qtmux->context);
1661
atom_moov_add_trak (qtmux->moov, collect_pad->trak);
1663
/* set up pad functions */
1665
gst_pad_set_setcaps_function (newpad,
1666
GST_DEBUG_FUNCPTR (gst_qt_mux_audio_sink_set_caps));
1668
gst_pad_set_setcaps_function (newpad,
1669
GST_DEBUG_FUNCPTR (gst_qt_mux_video_sink_set_caps));
1671
/* FIXME: hacked way to override/extend the event function of
1672
* GstCollectPads; because it sets its own event function giving the
1673
* element no access to events.
1675
qtmux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
1676
gst_pad_set_event_function (newpad,
1677
GST_DEBUG_FUNCPTR (gst_qt_mux_sink_event));
1679
gst_pad_set_active (newpad, TRUE);
1680
gst_element_add_pad (element, newpad);
1686
gst_qt_mux_get_property (GObject * object,
1687
guint prop_id, GValue * value, GParamSpec * pspec)
1689
GstQTMux *qtmux = GST_QT_MUX_CAST (object);
1691
GST_OBJECT_LOCK (qtmux);
1693
case PROP_LARGE_FILE:
1694
g_value_set_boolean (value, qtmux->large_file);
1696
case PROP_MOVIE_TIMESCALE:
1697
g_value_set_uint (value, qtmux->timescale);
1700
g_value_set_boolean (value, qtmux->guess_pts);
1702
case PROP_FAST_START:
1703
g_value_set_boolean (value, qtmux->fast_start);
1705
case PROP_FAST_START_TEMP_FILE:
1706
g_value_set_string (value, qtmux->fast_start_file_path);
1709
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1712
GST_OBJECT_UNLOCK (qtmux);
1716
gst_qt_mux_generate_fast_start_file_path (GstQTMux * qtmux)
1720
if (qtmux->fast_start_file_path) {
1721
g_free (qtmux->fast_start_file_path);
1722
qtmux->fast_start_file_path = NULL;
1725
tmp = g_strdup_printf ("%s%d", "qtmux", g_random_int ());
1726
qtmux->fast_start_file_path = g_build_filename (g_get_tmp_dir (), tmp, NULL);
1731
gst_qt_mux_set_property (GObject * object,
1732
guint prop_id, const GValue * value, GParamSpec * pspec)
1734
GstQTMux *qtmux = GST_QT_MUX_CAST (object);
1736
GST_OBJECT_LOCK (qtmux);
1738
case PROP_LARGE_FILE:
1739
qtmux->large_file = g_value_get_boolean (value);
1741
case PROP_MOVIE_TIMESCALE:
1742
qtmux->timescale = g_value_get_uint (value);
1745
qtmux->guess_pts = g_value_get_boolean (value);
1747
case PROP_FAST_START:
1748
qtmux->fast_start = g_value_get_boolean (value);
1750
case PROP_FAST_START_TEMP_FILE:
1751
if (qtmux->fast_start_file_path) {
1752
g_free (qtmux->fast_start_file_path);
1754
qtmux->fast_start_file_path = g_value_dup_string (value);
1755
/* NULL means to generate a random one */
1756
if (!qtmux->fast_start_file_path) {
1757
gst_qt_mux_generate_fast_start_file_path (qtmux);
1761
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1764
GST_OBJECT_UNLOCK (qtmux);
1767
static GstStateChangeReturn
1768
gst_qt_mux_change_state (GstElement * element, GstStateChange transition)
1770
GstStateChangeReturn ret;
1771
GstQTMux *qtmux = GST_QT_MUX_CAST (element);
1773
switch (transition) {
1774
case GST_STATE_CHANGE_NULL_TO_READY:
1776
case GST_STATE_CHANGE_READY_TO_PAUSED:
1777
gst_collect_pads_start (qtmux->collect);
1778
qtmux->state = GST_QT_MUX_STATE_STARTED;
1780
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1782
case GST_STATE_CHANGE_PAUSED_TO_READY:
1783
gst_collect_pads_stop (qtmux->collect);
1789
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1791
switch (transition) {
1792
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1794
case GST_STATE_CHANGE_PAUSED_TO_READY:
1795
gst_qt_mux_reset (qtmux, TRUE);
1797
case GST_STATE_CHANGE_READY_TO_NULL:
1808
gst_qt_mux_register (GstPlugin * plugin)
1810
GTypeInfo typeinfo = {
1811
sizeof (GstQTMuxClass),
1812
(GBaseInitFunc) gst_qt_mux_base_init,
1814
(GClassInitFunc) gst_qt_mux_class_init,
1819
(GInstanceInitFunc) gst_qt_mux_init,
1821
static const GInterfaceInfo tag_setter_info = {
1825
GstQTMuxFormat format;
1826
GstQTMuxClassParams *params;
1829
GST_LOG ("Registering muxers");
1832
GstQTMuxFormatProp *prop;
1834
prop = &gst_qt_mux_format_list[i];
1835
format = prop->format;
1836
if (format == GST_QT_MUX_FORMAT_NONE)
1839
/* create a cache for these properties */
1840
params = g_new0 (GstQTMuxClassParams, 1);
1841
params->prop = prop;
1842
params->src_caps = gst_static_caps_get (&prop->src_caps);
1843
params->video_sink_caps = gst_static_caps_get (&prop->video_sink_caps);
1844
params->audio_sink_caps = gst_static_caps_get (&prop->audio_sink_caps);
1846
/* create the type now */
1847
type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo,
1849
g_type_set_qdata (type, GST_QT_MUX_PARAMS_QDATA, (gpointer) params);
1850
g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
1852
if (!gst_element_register (plugin, prop->name, GST_RANK_NONE, type))
1858
GST_LOG ("Finished registering muxers");
1864
gst_qt_mux_plugin_init (GstPlugin * plugin)
1866
GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
1868
return gst_qt_mux_register (plugin);
1871
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1874
"Quicktime Muxer plugin",
1875
gst_qt_mux_plugin_init, VERSION, "LGPL", "gsoc2008 package",
1876
"embedded.ufcg.edu.br")