~oah-dev/oah/gst-plugins-bad

« back to all changes in this revision

Viewing changes to gst/qtmux/gstqtmux.c

  • Committer: Haakon Sporsheim
  • Date: 2009-03-12 13:52:03 UTC
  • Revision ID: haakon.sporsheim@tandberg.com-20090312135203-i5k294hgkushb0mt
Initial import of git repository: git://anongit.freedesktop.org/gstreamer/gst-plugins-bad (tag: RELEASE-0_10_10)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Quicktime muxer plugin for GStreamer
 
2
 * Copyright (C) 2008 Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>
 
3
 * Copyright (C) 2008 Mark Nauwelaerts <mnauw@users.sf.net>
 
4
 *
 
5
 * This library is free software; you can redistribute it and/or
 
6
 * modify it under the terms of the GNU Library General Public
 
7
 * License as published by the Free Software Foundation; either
 
8
 * version 2 of the License, or (at your option) any later version.
 
9
 *
 
10
 * This library is distributed in the hope that it will be useful,
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
13
 * Library General Public License for more details.
 
14
 *
 
15
 * You should have received a copy of the GNU Library General Public
 
16
 * License along with this library; if not, write to the
 
17
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 
18
 * Boston, MA 02111-1307, USA.
 
19
 */
 
20
/*
 
21
 * Unless otherwise indicated, Source Code is licensed under MIT license.
 
22
 * See further explanation attached in License Statement (distributed in the file
 
23
 * LICENSE).
 
24
 *
 
25
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 
26
 * this software and associated documentation files (the "Software"), to deal in
 
27
 * the Software without restriction, including without limitation the rights to
 
28
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 
29
 * of the Software, and to permit persons to whom the Software is furnished to do
 
30
 * so, subject to the following conditions:
 
31
 *
 
32
 * The above copyright notice and this permission notice shall be included in all
 
33
 * copies or substantial portions of the Software.
 
34
 *
 
35
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
36
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
37
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
38
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
39
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
40
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 
41
 * SOFTWARE.
 
42
 */
 
43
 
 
44
 
 
45
/**
 
46
 * SECTION:gstqtmux
 
47
 * @short_description: Muxer for quicktime(.mov) files
 
48
 *
 
49
 * <refsect2>
 
50
 * <para>
 
51
 * This element merges streams (audio and video) into qt(.mov) files.
 
52
 * </para>
 
53
 * <title>Example pipelines</title>
 
54
 * <para>
 
55
 * <programlisting>
 
56
 * gst-launch v4l2src num-buffers=500 ! video/x-raw-yuv,width=320,height=240 ! ffmpegcolorspace ! qtmux ! filesink location=video.mov
 
57
 * </programlisting>
 
58
 * Records a video stream captured from a v4l2 device and muxes it into a qt file.
 
59
 * </para>
 
60
 * </refsect2>
 
61
 *
 
62
 * Last reviewed on 2008-08-27
 
63
 */
 
64
 
 
65
/*
 
66
 * Based on avimux
 
67
 */
 
68
 
 
69
#ifdef HAVE_CONFIG_H
 
70
#include "config.h"
 
71
#endif
 
72
 
 
73
#include <glib/gstdio.h>
 
74
 
 
75
#include <gst/gst.h>
 
76
#include <gst/base/gstcollectpads.h>
 
77
 
 
78
#include "gstqtmux.h"
 
79
 
 
80
GST_DEBUG_CATEGORY_STATIC (gst_qt_mux_debug);
 
81
#define GST_CAT_DEFAULT gst_qt_mux_debug
 
82
 
 
83
/* QTMux signals and args */
 
84
enum
 
85
{
 
86
  /* FILL ME */
 
87
  LAST_SIGNAL
 
88
};
 
89
 
 
90
enum
 
91
{
 
92
  PROP_0,
 
93
  PROP_LARGE_FILE,
 
94
  PROP_MOVIE_TIMESCALE,
 
95
  PROP_DO_CTTS,
 
96
  PROP_FLAVOR,
 
97
  PROP_FAST_START,
 
98
  PROP_FAST_START_TEMP_FILE
 
99
};
 
100
 
 
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
 
107
 
 
108
static void gst_qt_mux_finalize (GObject * object);
 
109
 
 
110
static GstStateChangeReturn gst_qt_mux_change_state (GstElement * element,
 
111
    GstStateChange transition);
 
112
 
 
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);
 
118
 
 
119
/* pad functions */
 
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);
 
123
 
 
124
/* event */
 
125
static gboolean gst_qt_mux_sink_event (GstPad * pad, GstEvent * event);
 
126
 
 
127
static GstFlowReturn gst_qt_mux_collected (GstCollectPads * pads,
 
128
    gpointer user_data);
 
129
static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
 
130
    GstBuffer * buf);
 
131
 
 
132
static GstElementClass *parent_class = NULL;
 
133
 
 
134
static void
 
135
gst_qt_mux_base_init (gpointer g_class)
 
136
{
 
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;
 
142
 
 
143
  params =
 
144
      (GstQTMuxClassParams *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (g_class),
 
145
      GST_QT_MUX_PARAMS_QDATA);
 
146
  g_assert (params != NULL);
 
147
 
 
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);
 
159
 
 
160
  /* pad templates */
 
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);
 
164
 
 
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);
 
169
  }
 
170
 
 
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);
 
175
  }
 
176
 
 
177
  klass->format = params->prop->format;
 
178
}
 
179
 
 
180
static void
 
181
gst_qt_mux_class_init (GstQTMuxClass * klass)
 
182
{
 
183
  GObjectClass *gobject_class;
 
184
  GstElementClass *gstelement_class;
 
185
 
 
186
  gobject_class = (GObjectClass *) klass;
 
187
  gstelement_class = (GstElementClass *) klass;
 
188
 
 
189
  parent_class = g_type_class_peek_parent (klass);
 
190
 
 
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;
 
194
 
 
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));
 
220
 
 
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);
 
225
}
 
226
 
 
227
static void
 
228
gst_qt_mux_pad_reset (GstQTPad * qtpad)
 
229
{
 
230
  qtpad->fourcc = 0;
 
231
  qtpad->is_out_of_order = FALSE;
 
232
  qtpad->have_dts = FALSE;
 
233
  qtpad->sample_size = 0;
 
234
  qtpad->sync = FALSE;
 
235
  qtpad->last_dts = 0;
 
236
 
 
237
  if (qtpad->last_buf)
 
238
    gst_buffer_replace (&qtpad->last_buf, NULL);
 
239
 
 
240
  /* reference owned elsewhere */
 
241
  qtpad->trak = NULL;
 
242
}
 
243
 
 
244
/*
 
245
 * Takes GstQTMux back to its initial state
 
246
 */
 
247
static void
 
248
gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
 
249
{
 
250
  GSList *walk;
 
251
 
 
252
  qtmux->state = GST_QT_MUX_STATE_NONE;
 
253
  qtmux->header_size = 0;
 
254
  qtmux->mdat_size = 0;
 
255
  qtmux->mdat_pos = 0;
 
256
 
 
257
  if (qtmux->ftyp) {
 
258
    atom_ftyp_free (qtmux->ftyp);
 
259
    qtmux->ftyp = NULL;
 
260
  }
 
261
  if (qtmux->moov) {
 
262
    atom_moov_free (qtmux->moov);
 
263
    qtmux->moov = NULL;
 
264
  }
 
265
  if (qtmux->fast_start_file) {
 
266
    fclose (qtmux->fast_start_file);
 
267
    qtmux->fast_start_file = NULL;
 
268
  }
 
269
  gst_tag_setter_reset_tags (GST_TAG_SETTER (qtmux));
 
270
 
 
271
  /* reset pad data */
 
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);
 
275
 
 
276
    /* hm, moov_free above yanked the traks away from us,
 
277
     * so do not free, but do clear */
 
278
    qtpad->trak = NULL;
 
279
  }
 
280
 
 
281
  if (alloc) {
 
282
    qtmux->moov = atom_moov_new (qtmux->context);
 
283
  }
 
284
}
 
285
 
 
286
static void
 
287
gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
 
288
{
 
289
  GstElementClass *klass = GST_ELEMENT_CLASS (qtmux_klass);
 
290
  GstPadTemplate *templ;
 
291
  GstCaps *caps;
 
292
 
 
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);
 
300
 
 
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);
 
304
 
 
305
  /* properties set to default upon construction */
 
306
 
 
307
  /* always need this */
 
308
  qtmux->context =
 
309
      atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format));
 
310
 
 
311
  /* internals to initial state */
 
312
  gst_qt_mux_reset (qtmux, TRUE);
 
313
}
 
314
 
 
315
 
 
316
static void
 
317
gst_qt_mux_finalize (GObject * object)
 
318
{
 
319
  GstQTMux *qtmux = GST_QT_MUX_CAST (object);
 
320
 
 
321
  gst_qt_mux_reset (qtmux, FALSE);
 
322
 
 
323
  if (qtmux->fast_start_file_path)
 
324
    g_free (qtmux->fast_start_file_path);
 
325
 
 
326
  atoms_context_free (qtmux->context);
 
327
  gst_object_unref (qtmux->collect);
 
328
 
 
329
  G_OBJECT_CLASS (parent_class)->finalize (object);
 
330
}
 
331
 
 
332
/* FIXME approach below is pretty Apple/MOV/MP4/iTunes specific,
 
333
 * and as such does not comply with e.g. 3GPP specs */
 
334
 
 
335
/*
 
336
 * Struct to record mappings from gstreamer tags to fourcc codes
 
337
 */
 
338
typedef struct _GstTagToFourcc
 
339
{
 
340
  guint32 fourcc;
 
341
  const gchar *gsttag;
 
342
  const gchar *gsttag2;
 
343
} GstTagToFourcc;
 
344
 
 
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,},
 
362
  {0, NULL,}
 
363
};
 
364
 
 
365
/* qtdemux produces these for atoms it cannot parse */
 
366
#define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag"
 
367
 
 
368
static void
 
369
gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list)
 
370
{
 
371
  guint32 fourcc;
 
372
  gint i;
 
373
  const gchar *tag, *tag2;
 
374
 
 
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;
 
379
 
 
380
    switch (gst_tag_get_type (tag)) {
 
381
        /* strings */
 
382
      case G_TYPE_STRING:
 
383
      {
 
384
        gchar *str = NULL;
 
385
 
 
386
        if (!gst_tag_list_get_string (list, tag, &str) || !str)
 
387
          break;
 
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);
 
391
        g_free (str);
 
392
        break;
 
393
      }
 
394
        /* double */
 
395
      case G_TYPE_DOUBLE:
 
396
      {
 
397
        gdouble value;
 
398
 
 
399
        if (!gst_tag_list_get_double (list, tag, &value))
 
400
          break;
 
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);
 
404
        break;
 
405
      }
 
406
        /* paired unsigned integers */
 
407
      case G_TYPE_UINT:
 
408
      {
 
409
        guint value;
 
410
        guint count;
 
411
 
 
412
        if (!gst_tag_list_get_uint (list, tag, &value) ||
 
413
            !gst_tag_list_get_uint (list, tag2, &count))
 
414
          break;
 
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));
 
419
        break;
 
420
      }
 
421
      default:
 
422
      {
 
423
        if (gst_tag_get_type (tag) == GST_TYPE_DATE) {
 
424
          GDate *date = NULL;
 
425
          GDateYear year;
 
426
          GDateMonth month;
 
427
          GDateDay day;
 
428
          gchar *str;
 
429
 
 
430
          if (!gst_tag_list_get_date (list, tag, &date) || !date)
 
431
            break;
 
432
          year = g_date_get_year (date);
 
433
          month = g_date_get_month (date);
 
434
          day = g_date_get_day (date);
 
435
 
 
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");
 
439
            break;
 
440
          }
 
441
 
 
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, };
 
448
          GstBuffer *buf;
 
449
          GstCaps *caps;
 
450
          GstStructure *structure;
 
451
          gint flags = 0;
 
452
 
 
453
          if (!gst_tag_list_copy_value (&value, list, tag))
 
454
            break;
 
455
 
 
456
          buf = gst_value_get_buffer (&value);
 
457
          if (!buf)
 
458
            goto done;
 
459
 
 
460
          caps = gst_buffer_get_caps (buf);
 
461
          if (!caps) {
 
462
            GST_WARNING_OBJECT (qtmux, "preview image without caps");
 
463
            goto done;
 
464
          }
 
465
 
 
466
          GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);
 
467
 
 
468
          structure = gst_caps_get_structure (caps, 0);
 
469
          if (gst_structure_has_name (structure, "image/jpeg"))
 
470
            flags = 13;
 
471
          else if (gst_structure_has_name (structure, "image/png"))
 
472
            flags = 14;
 
473
          gst_caps_unref (caps);
 
474
 
 
475
          if (!flags) {
 
476
            GST_WARNING_OBJECT (qtmux, "preview image format not supported");
 
477
            goto done;
 
478
          }
 
479
 
 
480
          GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT
 
481
              " -> image size %d", GST_FOURCC_ARGS (fourcc),
 
482
              GST_BUFFER_SIZE (buf));
 
483
          atom_moov_add_tag (qtmux->moov, fourcc, flags, GST_BUFFER_DATA (buf),
 
484
              GST_BUFFER_SIZE (buf));
 
485
        done:
 
486
          g_value_unset (&value);
 
487
        } else
 
488
          g_assert_not_reached ();
 
489
        break;
 
490
      }
 
491
    }
 
492
  }
 
493
 
 
494
  /* add unparsed blobs if present */
 
495
  if (gst_tag_exists (GST_QT_DEMUX_PRIVATE_TAG)) {
 
496
    guint num_tags;
 
497
 
 
498
    num_tags = gst_tag_list_get_tag_size (list, GST_QT_DEMUX_PRIVATE_TAG);
 
499
    for (i = 0; i < num_tags; ++i) {
 
500
      const GValue *val;
 
501
      GstBuffer *buf;
 
502
      GstCaps *caps = NULL;
 
503
 
 
504
      val = gst_tag_list_get_value_index (list, GST_QT_DEMUX_PRIVATE_TAG, i);
 
505
      buf = (GstBuffer *) gst_value_get_mini_object (val);
 
506
 
 
507
      if (buf && (caps = gst_buffer_get_caps (buf))) {
 
508
        GstStructure *s;
 
509
        const gchar *style = NULL;
 
510
 
 
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));
 
520
          }
 
521
        }
 
522
        gst_caps_unref (caps);
 
523
      }
 
524
    }
 
525
  }
 
526
 
 
527
  return;
 
528
}
 
529
 
 
530
/*
 
531
 * Gets the tagsetter iface taglist and puts the known tags
 
532
 * into the output stream
 
533
 */
 
534
static void
 
535
gst_qt_mux_setup_metadata (GstQTMux * qtmux)
 
536
{
 
537
  const GstTagList *tags;
 
538
 
 
539
  tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (qtmux));
 
540
 
 
541
  GST_LOG_OBJECT (qtmux, "tags: %" GST_PTR_FORMAT, tags);
 
542
 
 
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);
 
546
  } else {
 
547
    GST_DEBUG_OBJECT (qtmux, "No tags received");
 
548
  }
 
549
}
 
550
 
 
551
static GstFlowReturn
 
552
gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset,
 
553
    gboolean mind_fast)
 
554
{
 
555
  GstFlowReturn res;
 
556
  guint8 *data;
 
557
  guint size;
 
558
 
 
559
  g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
 
560
 
 
561
  data = GST_BUFFER_DATA (buf);
 
562
  size = GST_BUFFER_SIZE (buf);
 
563
 
 
564
  GST_LOG_OBJECT (qtmux, "sending buffer size %d", size);
 
565
 
 
566
  if (mind_fast && qtmux->fast_start_file) {
 
567
    gint ret;
 
568
 
 
569
    GST_LOG_OBJECT (qtmux, "to temporary file");
 
570
    ret = fwrite (data, sizeof (guint8), size, qtmux->fast_start_file);
 
571
    gst_buffer_unref (buf);
 
572
    if (ret != size)
 
573
      goto write_error;
 
574
    else
 
575
      res = GST_FLOW_OK;
 
576
  } else {
 
577
    GST_LOG_OBJECT (qtmux, "downstream");
 
578
 
 
579
    gst_buffer_set_caps (buf, GST_PAD_CAPS (qtmux->srcpad));
 
580
    res = gst_pad_push (qtmux->srcpad, buf);
 
581
  }
 
582
 
 
583
  if (offset)
 
584
    *offset += size;
 
585
 
 
586
  return res;
 
587
 
 
588
  /* ERRORS */
 
589
write_error:
 
590
  {
 
591
    GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
 
592
        ("Failed to write to temporary file"), GST_ERROR_SYSTEM);
 
593
    return GST_FLOW_ERROR;
 
594
  }
 
595
}
 
596
 
 
597
static GstFlowReturn
 
598
gst_qt_mux_send_buffered_data (GstQTMux * qtmux, guint64 * offset)
 
599
{
 
600
  GstFlowReturn ret = GST_FLOW_OK;
 
601
  GstBuffer *buf = NULL;
 
602
 
 
603
  if (fflush (qtmux->fast_start_file))
 
604
    goto flush_failed;
 
605
 
 
606
  if (fseek (qtmux->fast_start_file, 0, SEEK_SET))
 
607
    goto seek_failed;
 
608
 
 
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) {
 
614
    gint r;
 
615
    const int bufsize = 4096;
 
616
 
 
617
    buf = gst_buffer_new_and_alloc (bufsize);
 
618
    r = fread (GST_BUFFER_DATA (buf), sizeof (guint8), bufsize,
 
619
        qtmux->fast_start_file);
 
620
    if (r == 0)
 
621
      break;
 
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);
 
625
    buf = NULL;
 
626
  }
 
627
  if (buf)
 
628
    gst_buffer_unref (buf);
 
629
 
 
630
exit:
 
631
  /* best cleaning up effort, eat possible error */
 
632
  fclose (qtmux->fast_start_file);
 
633
  qtmux->fast_start_file = NULL;
 
634
 
 
635
  /* FIXME maybe delete temporary file, or let the system handle that ? */
 
636
 
 
637
  return ret;
 
638
 
 
639
  /* ERRORS */
 
640
flush_failed:
 
641
  {
 
642
    GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
 
643
        ("Failed to flush temporary file"), GST_ERROR_SYSTEM);
 
644
    ret = GST_FLOW_ERROR;
 
645
    goto exit;
 
646
  }
 
647
seek_failed:
 
648
  {
 
649
    GST_ELEMENT_ERROR (qtmux, RESOURCE, SEEK,
 
650
        ("Failed to seek temporary file"), GST_ERROR_SYSTEM);
 
651
    ret = GST_FLOW_ERROR;
 
652
    goto exit;
 
653
  }
 
654
}
 
655
 
 
656
/*
 
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.
 
662
 */
 
663
static GstFlowReturn
 
664
gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size)
 
665
{
 
666
  Atom *node_header;
 
667
  GstBuffer *buf;
 
668
  guint8 *data = NULL;
 
669
  guint64 offset = 0;
 
670
 
 
671
  GST_DEBUG_OBJECT (qtmux, "Sending mdat's atom header, "
 
672
      "size %" G_GUINT64_FORMAT, size);
 
673
 
 
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;
 
679
  if (size)
 
680
    node_header->extended_size = size;
 
681
 
 
682
  size = offset = 0;
 
683
  if (atom_copy_data (node_header, &data, &size, &offset) == 0)
 
684
    goto serialize_error;
 
685
 
 
686
  buf = gst_buffer_new ();
 
687
  GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
 
688
  GST_BUFFER_SIZE (buf) = offset;
 
689
 
 
690
  g_free (node_header);
 
691
 
 
692
  GST_LOG_OBJECT (qtmux, "Pushing mdat start");
 
693
  return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
 
694
 
 
695
  /* ERRORS */
 
696
serialize_error:
 
697
  {
 
698
    GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
 
699
        ("Failed to serialize ftyp"));
 
700
    return GST_FLOW_ERROR;
 
701
  }
 
702
}
 
703
 
 
704
/*
 
705
 * We get the position of the mdat size field, seek back to it
 
706
 * and overwrite with the real value
 
707
 */
 
708
static GstFlowReturn
 
709
gst_qt_mux_update_mdat_size (GstQTMux * qtmux, guint64 mdat_pos,
 
710
    guint64 mdat_size, guint64 * offset)
 
711
{
 
712
  GstEvent *event;
 
713
  GstBuffer *buf;
 
714
 
 
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);
 
719
 
 
720
  buf = gst_buffer_new_and_alloc (sizeof (guint64));
 
721
  GST_WRITE_UINT64_BE (GST_BUFFER_DATA (buf), mdat_size);
 
722
 
 
723
  return gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
 
724
}
 
725
 
 
726
static GstFlowReturn
 
727
gst_qt_mux_stop_file (GstQTMux * qtmux)
 
728
{
 
729
  gboolean ret = GST_FLOW_OK;
 
730
  GstBuffer *buffer = NULL;
 
731
  guint64 offset = 0, size = 0;
 
732
  guint8 *data;
 
733
  GSList *walk;
 
734
  gboolean large_file;
 
735
  guint32 timescale;
 
736
 
 
737
  GST_DEBUG_OBJECT (qtmux, "Updating remaining values and sending last data");
 
738
 
 
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;
 
743
 
 
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));
 
752
  }
 
753
 
 
754
  GST_OBJECT_LOCK (qtmux);
 
755
  timescale = qtmux->timescale;
 
756
  large_file = qtmux->large_file;
 
757
  GST_OBJECT_UNLOCK (qtmux);
 
758
 
 
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,
 
764
      timescale);
 
765
  atom_moov_update_timescale (qtmux->moov, timescale);
 
766
  atom_moov_set_64bits (qtmux->moov, large_file);
 
767
  atom_moov_update_duration (qtmux->moov);
 
768
 
 
769
  /* tags into file metadata */
 
770
  gst_qt_mux_setup_metadata (qtmux);
 
771
 
 
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 */
 
776
    offset = size = 0;
 
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,
 
780
        size);
 
781
    offset += qtmux->header_size + MDAT_ATOM_HEADER_SIZE;
 
782
  } else
 
783
    offset = qtmux->header_size;
 
784
  atom_moov_chunks_add_offset (qtmux->moov, offset);
 
785
 
 
786
  /* serialize moov */
 
787
  offset = size = 0;
 
788
  data = NULL;
 
789
  GST_LOG_OBJECT (qtmux, "Copying movie header into buffer");
 
790
  ret = atom_moov_copy_data (qtmux->moov, &data, &size, &offset);
 
791
  if (!ret)
 
792
    goto serialize_error;
 
793
 
 
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);
 
801
 
 
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)
 
809
      return ret;
 
810
    ret = gst_qt_mux_send_buffered_data (qtmux, NULL);
 
811
    if (ret != GST_FLOW_OK)
 
812
      return ret;
 
813
  } else {
 
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 */
 
820
  }
 
821
 
 
822
  return ret;
 
823
 
 
824
  /* ERRORS */
 
825
serialize_error:
 
826
  {
 
827
    gst_buffer_unref (buffer);
 
828
    GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
 
829
        ("Failed to serialize moov"));
 
830
    return GST_FLOW_ERROR;
 
831
  }
 
832
}
 
833
 
 
834
static GstFlowReturn
 
835
gst_qt_mux_send_ftyp (GstQTMux * qtmux, guint64 * off)
 
836
{
 
837
  GstBuffer *buf;
 
838
  guint64 size = 0, offset = 0;
 
839
  guint8 *data = NULL;
 
840
 
 
841
  GST_DEBUG_OBJECT (qtmux, "Sending ftyp atom");
 
842
 
 
843
  if (!atom_ftyp_copy_data (qtmux->ftyp, &data, &size, &offset))
 
844
    goto serialize_error;
 
845
 
 
846
  buf = gst_buffer_new ();
 
847
  GST_BUFFER_DATA (buf) = GST_BUFFER_MALLOCDATA (buf) = data;
 
848
  GST_BUFFER_SIZE (buf) = offset;
 
849
 
 
850
  GST_LOG_OBJECT (qtmux, "Pushing ftyp");
 
851
  return gst_qt_mux_send_buffer (qtmux, buf, off, FALSE);
 
852
 
 
853
  /* ERRORS */
 
854
serialize_error:
 
855
  {
 
856
    GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
 
857
        ("Failed to serialize ftyp"));
 
858
    return GST_FLOW_ERROR;
 
859
  }
 
860
}
 
861
 
 
862
static GstFlowReturn
 
863
gst_qt_mux_start_file (GstQTMux * qtmux)
 
864
{
 
865
  GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
 
866
  GstFlowReturn ret = GST_FLOW_OK;
 
867
  guint32 major, version;
 
868
  GList *comp;
 
869
  GstBuffer *prefix;
 
870
 
 
871
  GST_DEBUG_OBJECT (qtmux, "starting file");
 
872
 
 
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));
 
876
 
 
877
  /* init and send context and ftyp based on current property state */
 
878
  if (qtmux->ftyp)
 
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);
 
883
  if (comp)
 
884
    g_list_free (comp);
 
885
  if (prefix) {
 
886
    ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE);
 
887
    if (ret != GST_FLOW_OK)
 
888
      goto exit;
 
889
  }
 
890
  ret = gst_qt_mux_send_ftyp (qtmux, &qtmux->header_size);
 
891
  if (ret != GST_FLOW_OK)
 
892
    goto exit;
 
893
 
 
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)
 
899
      goto open_failed;
 
900
  } else {
 
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);
 
904
  }
 
905
  GST_OBJECT_UNLOCK (qtmux);
 
906
 
 
907
exit:
 
908
  return ret;
 
909
 
 
910
  /* ERRORS */
 
911
open_failed:
 
912
  {
 
913
    GST_ELEMENT_ERROR (qtmux, RESOURCE, OPEN_READ_WRITE,
 
914
        (("Could not open temporary file \"%s\""), qtmux->fast_start_file_path),
 
915
        GST_ERROR_SYSTEM);
 
916
    GST_OBJECT_UNLOCK (qtmux);
 
917
    return GST_FLOW_ERROR;
 
918
  }
 
919
}
 
920
 
 
921
/*
 
922
 * Here we push the buffer and update the tables in the track atoms
 
923
 */
 
924
static GstFlowReturn
 
925
gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
 
926
{
 
927
  GstBuffer *last_buf = NULL;
 
928
  GstClockTime duration;
 
929
  guint nsamples, sample_size;
 
930
  guint64 scaled_duration, chunk_offset;
 
931
  gint64 last_dts;
 
932
  gint64 pts_offset = 0;
 
933
  gboolean sync = FALSE, do_pts = FALSE;
 
934
 
 
935
  if (!pad->fourcc)
 
936
    goto not_negotiated;
 
937
 
 
938
  last_buf = pad->last_buf;
 
939
  if (last_buf == NULL) {
 
940
#ifndef GST_DISABLE_GST_DEBUG
 
941
    if (buf == NULL) {
 
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));
 
945
    } else {
 
946
      GST_LOG_OBJECT (qtmux,
 
947
          "Pad %s has no previous buffer stored, storing now",
 
948
          GST_PAD_NAME (pad->collect.pad));
 
949
    }
 
950
#endif
 
951
    pad->last_buf = buf;
 
952
    return GST_FLOW_OK;
 
953
  } else
 
954
    gst_buffer_ref (last_buf);
 
955
 
 
956
  /* fall back to duration if:
 
957
   * - last bufer
 
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 */
 
965
      if (buf)
 
966
        goto no_time;
 
967
      GST_WARNING_OBJECT (qtmux, "no duration for last buffer");
 
968
      /* iso spec recommends some small value, try 0 */
 
969
      duration = 0;
 
970
    } else {
 
971
      duration = GST_BUFFER_DURATION (last_buf);
 
972
    }
 
973
  } else {
 
974
    duration = GST_BUFFER_TIMESTAMP (buf) - GST_BUFFER_TIMESTAMP (last_buf);
 
975
  }
 
976
 
 
977
  gst_buffer_replace (&pad->last_buf, buf);
 
978
 
 
979
  last_dts = gst_util_uint64_scale (pad->last_dts,
 
980
      atom_trak_get_timescale (pad->trak), GST_SECOND);
 
981
 
 
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 */
 
992
    scaled_duration = 1;
 
993
    pad->last_dts += duration * nsamples;
 
994
  } else {
 
995
    nsamples = 1;
 
996
    sample_size = GST_BUFFER_SIZE (last_buf);
 
997
    if (pad->have_dts) {
 
998
      gint64 scaled_dts;
 
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);
 
1003
      } else {
 
1004
        scaled_dts = gst_util_uint64_scale (pad->last_dts,
 
1005
            atom_trak_get_timescale (pad->trak), GST_SECOND);
 
1006
      }
 
1007
      scaled_duration = scaled_dts - last_dts;
 
1008
      last_dts = scaled_dts;
 
1009
    } else {
 
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;
 
1017
    }
 
1018
  }
 
1019
  chunk_offset = qtmux->mdat_size;
 
1020
 
 
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);
 
1028
 
 
1029
  /* might be a sync sample */
 
1030
  if (pad->sync &&
 
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));
 
1034
    sync = TRUE;
 
1035
  }
 
1036
 
 
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) {
 
1047
    guint64 pts;
 
1048
 
 
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);
 
1052
    do_pts = TRUE;
 
1053
    GST_LOG_OBJECT (qtmux, "Adding ctts entry for pad %s: %" G_GINT64_FORMAT,
 
1054
        GST_PAD_NAME (pad->collect.pad), pts_offset);
 
1055
  }
 
1056
 
 
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);
 
1061
 
 
1062
  if (buf)
 
1063
    gst_buffer_unref (buf);
 
1064
 
 
1065
  return gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE);
 
1066
 
 
1067
  /* ERRORS */
 
1068
bail:
 
1069
  {
 
1070
    if (buf)
 
1071
      gst_buffer_unref (buf);
 
1072
    gst_buffer_unref (last_buf);
 
1073
    return GST_FLOW_ERROR;
 
1074
  }
 
1075
no_time:
 
1076
  {
 
1077
    GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
 
1078
        ("Failed to determine time to mux."));
 
1079
    goto bail;
 
1080
  }
 
1081
fragmented_sample:
 
1082
  {
 
1083
    GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
 
1084
        ("Audio buffer contains fragmented sample."));
 
1085
    goto bail;
 
1086
  }
 
1087
not_negotiated:
 
1088
  {
 
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;
 
1094
  }
 
1095
}
 
1096
 
 
1097
static GstFlowReturn
 
1098
gst_qt_mux_collected (GstCollectPads * pads, gpointer user_data)
 
1099
{
 
1100
  GstFlowReturn ret = GST_FLOW_OK;
 
1101
  GstQTMux *qtmux = GST_QT_MUX_CAST (user_data);
 
1102
  GSList *walk;
 
1103
  GstQTPad *best_pad = NULL;
 
1104
  GstClockTime time, best_time = GST_CLOCK_TIME_NONE;
 
1105
  GstBuffer *buf;
 
1106
 
 
1107
  if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_STARTED)) {
 
1108
    if ((ret = gst_qt_mux_start_file (qtmux)) != GST_FLOW_OK)
 
1109
      return ret;
 
1110
    else
 
1111
      qtmux->state = GST_QT_MUX_STATE_DATA;
 
1112
  }
 
1113
 
 
1114
  if (G_UNLIKELY (qtmux->state == GST_QT_MUX_STATE_EOS))
 
1115
    return GST_FLOW_UNEXPECTED;
 
1116
 
 
1117
  /* select the best buffer */
 
1118
  walk = qtmux->collect->data;
 
1119
  while (walk) {
 
1120
    GstQTPad *pad;
 
1121
    GstCollectData *data;
 
1122
 
 
1123
    data = (GstCollectData *) walk->data;
 
1124
    pad = (GstQTPad *) data;
 
1125
 
 
1126
    walk = g_slist_next (walk);
 
1127
 
 
1128
    buf = gst_collect_pads_peek (pads, data);
 
1129
    if (buf == NULL) {
 
1130
      GST_LOG_OBJECT (qtmux, "Pad %s has no buffers",
 
1131
          GST_PAD_NAME (pad->collect.pad));
 
1132
      continue;
 
1133
    }
 
1134
    time = GST_BUFFER_TIMESTAMP (buf);
 
1135
    gst_buffer_unref (buf);
 
1136
 
 
1137
    if (best_pad == NULL || !GST_CLOCK_TIME_IS_VALID (time) ||
 
1138
        (GST_CLOCK_TIME_IS_VALID (best_time) && time < best_time)) {
 
1139
      best_pad = pad;
 
1140
      best_time = time;
 
1141
    }
 
1142
  }
 
1143
 
 
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);
 
1149
  } else {
 
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;
 
1154
    }
 
1155
    qtmux->state = GST_QT_MUX_STATE_EOS;
 
1156
  }
 
1157
 
 
1158
  return ret;
 
1159
}
 
1160
 
 
1161
static gboolean
 
1162
gst_qt_mux_audio_sink_set_caps (GstPad * pad, GstCaps * caps)
 
1163
{
 
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;
 
1176
 
 
1177
  /* find stream data */
 
1178
  qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
 
1179
  g_assert (qtpad);
 
1180
 
 
1181
  /* does not go well to renegotiate stream mid-way */
 
1182
  if (qtpad->fourcc)
 
1183
    goto refuse_renegotiation;
 
1184
 
 
1185
  GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
 
1186
      GST_DEBUG_PAD_NAME (pad), caps);
 
1187
 
 
1188
  format = qtmux_klass->format;
 
1189
  structure = gst_caps_get_structure (caps, 0);
 
1190
  mimetype = gst_structure_get_name (structure);
 
1191
 
 
1192
  /* common info */
 
1193
  if (!gst_structure_get_int (structure, "channels", &channels) ||
 
1194
      !gst_structure_get_int (structure, "rate", &rate)) {
 
1195
    goto refuse_caps;
 
1196
  }
 
1197
 
 
1198
  /* optional */
 
1199
  value = gst_structure_get_value (structure, "codec_data");
 
1200
  if (value != NULL)
 
1201
    codec_data = gst_value_get_buffer (value);
 
1202
 
 
1203
  qtpad->is_out_of_order = FALSE;
 
1204
  qtpad->have_dts = FALSE;
 
1205
 
 
1206
  /* set common properties */
 
1207
  entry.sample_rate = rate;
 
1208
  entry.channels = channels;
 
1209
  /* default */
 
1210
  entry.sample_size = 16;
 
1211
  /* this is the typical compressed case */
 
1212
  if (format == GST_QT_MUX_FORMAT_QT) {
 
1213
    entry.version = 1;
 
1214
    entry.compression_id = -2;
 
1215
  }
 
1216
 
 
1217
  /* now map onto a fourcc, and some extra properties */
 
1218
  if (strcmp (mimetype, "audio/mpeg") == 0) {
 
1219
    gint mpegversion = 0;
 
1220
    gint layer = -1;
 
1221
 
 
1222
    gst_structure_get_int (structure, "mpegversion", &mpegversion);
 
1223
    switch (mpegversion) {
 
1224
      case 1:
 
1225
        gst_structure_get_int (structure, "layer", &layer);
 
1226
        switch (layer) {
 
1227
          case 3:
 
1228
            /* mp3 */
 
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;
 
1232
            else {
 
1233
              entry.fourcc = FOURCC_mp4a;
 
1234
              ext_atom =
 
1235
                  build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG1_P3,
 
1236
                  ESDS_STREAM_TYPE_AUDIO, codec_data);
 
1237
            }
 
1238
            entry.samples_per_packet = 1152;
 
1239
            entry.bytes_per_sample = 2;
 
1240
            break;
 
1241
        }
 
1242
        break;
 
1243
      case 4:
 
1244
        /* AAC */
 
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");
 
1248
        else {
 
1249
          guint8 profile = GST_READ_UINT8 (GST_BUFFER_DATA (codec_data));
 
1250
 
 
1251
          /* warn if not Low Complexity profile */
 
1252
          profile >>= 3;
 
1253
          if (profile != 2)
 
1254
            GST_WARNING_OBJECT (qtmux,
 
1255
                "non-LC AAC may not run well on (Apple) QuickTime/iTunes");
 
1256
        }
 
1257
        if (format == GST_QT_MUX_FORMAT_QT)
 
1258
          ext_atom = build_mov_aac_extension (qtpad->trak, codec_data);
 
1259
        else
 
1260
          ext_atom =
 
1261
              build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P3,
 
1262
              ESDS_STREAM_TYPE_AUDIO, codec_data);
 
1263
        break;
 
1264
      default:
 
1265
        break;
 
1266
    }
 
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) {
 
1273
    gint width;
 
1274
    gint depth;
 
1275
    gint endianness;
 
1276
    gboolean sign;
 
1277
 
 
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");
 
1284
      goto refuse_caps;
 
1285
    }
 
1286
 
 
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!");
 
1290
      goto refuse_caps;
 
1291
    }
 
1292
 
 
1293
    if (sign) {
 
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 */
 
1299
      if (depth <= 16)
 
1300
        entry.version = 0;
 
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;
 
1310
    } else {
 
1311
      if (width == 8 && depth == 8) {
 
1312
        /* fall back to old 8-bit version */
 
1313
        entry.fourcc = FOURCC_raw_;
 
1314
        entry.version = 0;
 
1315
        entry.compression_id = 0;
 
1316
        entry.sample_size = 8;
 
1317
      } else {
 
1318
        GST_DEBUG_OBJECT (qtmux, "non 8-bit PCM must be signed");
 
1319
        goto refuse_caps;
 
1320
      }
 
1321
    }
 
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;
 
1331
  }
 
1332
 
 
1333
  if (!entry.fourcc)
 
1334
    goto refuse_caps;
 
1335
 
 
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);
 
1341
 
 
1342
  gst_object_unref (qtmux);
 
1343
  return TRUE;
 
1344
 
 
1345
  /* ERRORS */
 
1346
refuse_caps:
 
1347
  {
 
1348
    GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
 
1349
        GST_PAD_NAME (pad), caps);
 
1350
    gst_object_unref (qtmux);
 
1351
    return FALSE;
 
1352
  }
 
1353
refuse_renegotiation:
 
1354
  {
 
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);
 
1359
    return FALSE;
 
1360
  }
 
1361
}
 
1362
 
 
1363
/* scale rate up or down by factor of 10 to fit into [1000,10000] interval */
 
1364
static guint32
 
1365
adjust_rate (guint64 rate)
 
1366
{
 
1367
  while (rate >= 10000)
 
1368
    rate /= 10;
 
1369
 
 
1370
  while (rate < 1000)
 
1371
    rate *= 10;
 
1372
 
 
1373
  return (guint32) rate;
 
1374
}
 
1375
 
 
1376
static gboolean
 
1377
gst_qt_mux_video_sink_set_caps (GstPad * pad, GstCaps * caps)
 
1378
{
 
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;
 
1386
  guint32 rate;
 
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;
 
1394
 
 
1395
  /* find stream data */
 
1396
  qtpad = (GstQTPad *) gst_pad_get_element_private (pad);
 
1397
  g_assert (qtpad);
 
1398
 
 
1399
  /* does not go well to renegotiate stream mid-way */
 
1400
  if (qtpad->fourcc)
 
1401
    goto refuse_renegotiation;
 
1402
 
 
1403
  GST_DEBUG_OBJECT (qtmux, "%s:%s, caps=%" GST_PTR_FORMAT,
 
1404
      GST_DEBUG_PAD_NAME (pad), caps);
 
1405
 
 
1406
  format = qtmux_klass->format;
 
1407
  structure = gst_caps_get_structure (caps, 0);
 
1408
  mimetype = gst_structure_get_name (structure);
 
1409
 
 
1410
  /* required parts */
 
1411
  if (!gst_structure_get_int (structure, "width", &width) ||
 
1412
      !gst_structure_get_int (structure, "height", &height))
 
1413
    goto refuse_caps;
 
1414
 
 
1415
  /* optional */
 
1416
  depth = -1;
 
1417
  /* works as a default timebase */
 
1418
  framerate_num = 10000;
 
1419
  framerate_den = 1;
 
1420
  gst_structure_get_fraction (structure, "framerate", &framerate_num,
 
1421
      &framerate_den);
 
1422
  gst_structure_get_int (structure, "depth", &depth);
 
1423
  value = gst_structure_get_value (structure, "codec_data");
 
1424
  if (value != NULL)
 
1425
    codec_data = gst_value_get_buffer (value);
 
1426
 
 
1427
  par_num = 1;
 
1428
  par_den = 1;
 
1429
  gst_structure_get_fraction (structure, "pixel-aspect-ratio", &par_num,
 
1430
      &par_den);
 
1431
  /* FIXME: pixel-aspect-ratio */
 
1432
 
 
1433
  qtpad->is_out_of_order = FALSE;
 
1434
 
 
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,
 
1439
      rate);
 
1440
 
 
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;
 
1447
  entry.depth = 24;
 
1448
 
 
1449
  /* sync entries by default */
 
1450
  sync = TRUE;
 
1451
 
 
1452
  /* now map onto a fourcc, and some extra properties */
 
1453
  if (strcmp (mimetype, "video/x-raw-rgb") == 0) {
 
1454
    gint bpp;
 
1455
 
 
1456
    entry.fourcc = FOURCC_raw_;
 
1457
    gst_structure_get_int (structure, "bpp", &bpp);
 
1458
    entry.depth = bpp;
 
1459
    sync = FALSE;
 
1460
  } else if (strcmp (mimetype, "video/x-raw-yuv") == 0) {
 
1461
    guint32 format = 0;
 
1462
 
 
1463
    sync = FALSE;
 
1464
    gst_structure_get_fourcc (structure, "format", &format);
 
1465
    switch (format) {
 
1466
      case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
 
1467
        if (depth == -1)
 
1468
          depth = 24;
 
1469
        entry.fourcc = FOURCC_2vuy;
 
1470
        entry.depth = depth;
 
1471
        break;
 
1472
    }
 
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) {
 
1477
    gint version = 0;
 
1478
 
 
1479
    if (strcmp (mimetype, "video/x-divx") == 0) {
 
1480
      gst_structure_get_int (structure, "divxversion", &version);
 
1481
      version = version == 5 ? 1 : 0;
 
1482
    } else {
 
1483
      gst_structure_get_int (structure, "mpegversion", &version);
 
1484
      version = version == 4 ? 1 : 0;
 
1485
    }
 
1486
    if (version) {
 
1487
      entry.fourcc = FOURCC_mp4v;
 
1488
      ext_atom =
 
1489
          build_esds_extension (qtpad->trak, ESDS_OBJECT_TYPE_MPEG4_P2,
 
1490
          ESDS_STREAM_TYPE_VISUAL, codec_data);
 
1491
      if (!codec_data)
 
1492
        GST_WARNING_OBJECT (qtmux, "no codec_data for MPEG4 video; "
 
1493
            "output might not play in Apple QuickTime (try global-headers?)");
 
1494
    }
 
1495
  } else if (strcmp (mimetype, "video/x-h264") == 0) {
 
1496
    entry.fourcc = FOURCC_avc1;
 
1497
    qtpad->is_out_of_order = TRUE;
 
1498
    if (!codec_data)
 
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) {
 
1502
    gint version = 0;
 
1503
    gboolean pal = TRUE;
 
1504
 
 
1505
    sync = FALSE;
 
1506
    if (framerate_num != 25 || framerate_den != 1)
 
1507
      pal = FALSE;
 
1508
    gst_structure_get_int (structure, "dvversion", &version);
 
1509
    /* fall back to typical one */
 
1510
    if (!version)
 
1511
      version = 25;
 
1512
    switch (version) {
 
1513
      case 25:
 
1514
        if (pal)
 
1515
          entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', 'p');
 
1516
        else
 
1517
          entry.fourcc = GST_MAKE_FOURCC ('d', 'v', 'c', ' ');
 
1518
        break;
 
1519
      case 50:
 
1520
        if (pal)
 
1521
          entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'p');
 
1522
        else
 
1523
          entry.fourcc = GST_MAKE_FOURCC ('d', 'v', '5', 'n');
 
1524
        break;
 
1525
      default:
 
1526
        GST_WARNING_OBJECT (qtmux, "unrecognized dv version");
 
1527
        break;
 
1528
    }
 
1529
  } else if (strcmp (mimetype, "image/jpeg") == 0) {
 
1530
    entry.fourcc = FOURCC_jpeg;
 
1531
    sync = FALSE;
 
1532
  } else if (strcmp (mimetype, "image/x-j2c") == 0) {
 
1533
    guint32 fourcc;
 
1534
 
 
1535
    entry.fourcc = FOURCC_mjp2;
 
1536
    sync = FALSE;
 
1537
    if (!gst_structure_get_fourcc (structure, "fourcc", &fourcc) ||
 
1538
        !(ext_atom =
 
1539
            build_jp2h_extension (qtpad->trak, width, height, fourcc))) {
 
1540
      GST_DEBUG_OBJECT (qtmux, "missing or invalid fourcc in jp2 caps");
 
1541
      goto refuse_caps;
 
1542
    }
 
1543
  } else if (strcmp (mimetype, "video/x-qt-part") == 0) {
 
1544
    guint32 fourcc;
 
1545
 
 
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) {
 
1551
    guint32 fourcc;
 
1552
 
 
1553
    gst_structure_get_fourcc (structure, "format", &fourcc);
 
1554
    entry.fourcc = fourcc;
 
1555
    qtpad->is_out_of_order = TRUE;
 
1556
    qtpad->have_dts = TRUE;
 
1557
  }
 
1558
 
 
1559
  if (!entry.fourcc)
 
1560
    goto refuse_caps;
 
1561
 
 
1562
  /* ok, set the pad info accordingly */
 
1563
  qtpad->fourcc = entry.fourcc;
 
1564
  qtpad->sync = sync;
 
1565
  atom_trak_set_video_type (qtpad->trak, qtmux->context, &entry, rate,
 
1566
      ext_atom);
 
1567
 
 
1568
  gst_object_unref (qtmux);
 
1569
  return TRUE;
 
1570
 
 
1571
  /* ERRORS */
 
1572
refuse_caps:
 
1573
  {
 
1574
    GST_WARNING_OBJECT (qtmux, "pad %s refused caps %" GST_PTR_FORMAT,
 
1575
        GST_PAD_NAME (pad), caps);
 
1576
    gst_object_unref (qtmux);
 
1577
    return FALSE;
 
1578
  }
 
1579
refuse_renegotiation:
 
1580
  {
 
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);
 
1585
    return FALSE;
 
1586
  }
 
1587
}
 
1588
 
 
1589
static gboolean
 
1590
gst_qt_mux_sink_event (GstPad * pad, GstEvent * event)
 
1591
{
 
1592
  gboolean ret;
 
1593
  GstQTMux *qtmux;
 
1594
 
 
1595
  qtmux = GST_QT_MUX_CAST (gst_pad_get_parent (pad));
 
1596
  switch (GST_EVENT_TYPE (event)) {
 
1597
    case GST_EVENT_TAG:{
 
1598
      GstTagList *list;
 
1599
      GstTagSetter *setter = GST_TAG_SETTER (qtmux);
 
1600
      const GstTagMergeMode mode = gst_tag_setter_get_tag_merge_mode (setter);
 
1601
 
 
1602
      GST_DEBUG_OBJECT (qtmux, "received tag event");
 
1603
      gst_event_parse_tag (event, &list);
 
1604
      gst_tag_setter_merge_tags (setter, list, mode);
 
1605
      break;
 
1606
    }
 
1607
    default:
 
1608
      break;
 
1609
  }
 
1610
 
 
1611
  ret = qtmux->collect_event (pad, event);
 
1612
  gst_object_unref (qtmux);
 
1613
 
 
1614
  return ret;
 
1615
}
 
1616
 
 
1617
static void
 
1618
gst_qt_mux_release_pad (GstElement * element, GstPad * pad)
 
1619
{
 
1620
  GstQTMux *mux = GST_QT_MUX_CAST (element);
 
1621
 
 
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);
 
1625
}
 
1626
 
 
1627
static GstPad *
 
1628
gst_qt_mux_request_new_pad (GstElement * element,
 
1629
    GstPadTemplate * templ, const gchar * name)
 
1630
{
 
1631
  GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
 
1632
  GstQTMux *qtmux = GST_QT_MUX_CAST (element);
 
1633
  GstQTPad *collect_pad;
 
1634
  GstPad *newpad;
 
1635
  gboolean audio;
 
1636
 
 
1637
  GST_DEBUG_OBJECT (qtmux, "Requested pad: %s", GST_STR_NULL (name));
 
1638
 
 
1639
  if (qtmux->state != GST_QT_MUX_STATE_NONE) {
 
1640
    GST_WARNING_OBJECT (qtmux, "Not providing request pad after stream start.");
 
1641
    return NULL;
 
1642
  }
 
1643
 
 
1644
  if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
 
1645
    audio = TRUE;
 
1646
  } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
 
1647
    audio = FALSE;
 
1648
  } else {
 
1649
    GST_WARNING_OBJECT (qtmux, "This is not our template!");
 
1650
    return NULL;
 
1651
  }
 
1652
 
 
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));
 
1658
  /* set up pad */
 
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);
 
1662
 
 
1663
  /* set up pad functions */
 
1664
  if (audio)
 
1665
    gst_pad_set_setcaps_function (newpad,
 
1666
        GST_DEBUG_FUNCPTR (gst_qt_mux_audio_sink_set_caps));
 
1667
  else
 
1668
    gst_pad_set_setcaps_function (newpad,
 
1669
        GST_DEBUG_FUNCPTR (gst_qt_mux_video_sink_set_caps));
 
1670
 
 
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.
 
1674
   */
 
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));
 
1678
 
 
1679
  gst_pad_set_active (newpad, TRUE);
 
1680
  gst_element_add_pad (element, newpad);
 
1681
 
 
1682
  return newpad;
 
1683
}
 
1684
 
 
1685
static void
 
1686
gst_qt_mux_get_property (GObject * object,
 
1687
    guint prop_id, GValue * value, GParamSpec * pspec)
 
1688
{
 
1689
  GstQTMux *qtmux = GST_QT_MUX_CAST (object);
 
1690
 
 
1691
  GST_OBJECT_LOCK (qtmux);
 
1692
  switch (prop_id) {
 
1693
    case PROP_LARGE_FILE:
 
1694
      g_value_set_boolean (value, qtmux->large_file);
 
1695
      break;
 
1696
    case PROP_MOVIE_TIMESCALE:
 
1697
      g_value_set_uint (value, qtmux->timescale);
 
1698
      break;
 
1699
    case PROP_DO_CTTS:
 
1700
      g_value_set_boolean (value, qtmux->guess_pts);
 
1701
      break;
 
1702
    case PROP_FAST_START:
 
1703
      g_value_set_boolean (value, qtmux->fast_start);
 
1704
      break;
 
1705
    case PROP_FAST_START_TEMP_FILE:
 
1706
      g_value_set_string (value, qtmux->fast_start_file_path);
 
1707
      break;
 
1708
    default:
 
1709
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
1710
      break;
 
1711
  }
 
1712
  GST_OBJECT_UNLOCK (qtmux);
 
1713
}
 
1714
 
 
1715
static void
 
1716
gst_qt_mux_generate_fast_start_file_path (GstQTMux * qtmux)
 
1717
{
 
1718
  gchar *tmp;
 
1719
 
 
1720
  if (qtmux->fast_start_file_path) {
 
1721
    g_free (qtmux->fast_start_file_path);
 
1722
    qtmux->fast_start_file_path = NULL;
 
1723
  }
 
1724
 
 
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);
 
1727
  g_free (tmp);
 
1728
}
 
1729
 
 
1730
static void
 
1731
gst_qt_mux_set_property (GObject * object,
 
1732
    guint prop_id, const GValue * value, GParamSpec * pspec)
 
1733
{
 
1734
  GstQTMux *qtmux = GST_QT_MUX_CAST (object);
 
1735
 
 
1736
  GST_OBJECT_LOCK (qtmux);
 
1737
  switch (prop_id) {
 
1738
    case PROP_LARGE_FILE:
 
1739
      qtmux->large_file = g_value_get_boolean (value);
 
1740
      break;
 
1741
    case PROP_MOVIE_TIMESCALE:
 
1742
      qtmux->timescale = g_value_get_uint (value);
 
1743
      break;
 
1744
    case PROP_DO_CTTS:
 
1745
      qtmux->guess_pts = g_value_get_boolean (value);
 
1746
      break;
 
1747
    case PROP_FAST_START:
 
1748
      qtmux->fast_start = g_value_get_boolean (value);
 
1749
      break;
 
1750
    case PROP_FAST_START_TEMP_FILE:
 
1751
      if (qtmux->fast_start_file_path) {
 
1752
        g_free (qtmux->fast_start_file_path);
 
1753
      }
 
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);
 
1758
      }
 
1759
      break;
 
1760
    default:
 
1761
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 
1762
      break;
 
1763
  }
 
1764
  GST_OBJECT_UNLOCK (qtmux);
 
1765
}
 
1766
 
 
1767
static GstStateChangeReturn
 
1768
gst_qt_mux_change_state (GstElement * element, GstStateChange transition)
 
1769
{
 
1770
  GstStateChangeReturn ret;
 
1771
  GstQTMux *qtmux = GST_QT_MUX_CAST (element);
 
1772
 
 
1773
  switch (transition) {
 
1774
    case GST_STATE_CHANGE_NULL_TO_READY:
 
1775
      break;
 
1776
    case GST_STATE_CHANGE_READY_TO_PAUSED:
 
1777
      gst_collect_pads_start (qtmux->collect);
 
1778
      qtmux->state = GST_QT_MUX_STATE_STARTED;
 
1779
      break;
 
1780
    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
 
1781
      break;
 
1782
    case GST_STATE_CHANGE_PAUSED_TO_READY:
 
1783
      gst_collect_pads_stop (qtmux->collect);
 
1784
      break;
 
1785
    default:
 
1786
      break;
 
1787
  }
 
1788
 
 
1789
  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
 
1790
 
 
1791
  switch (transition) {
 
1792
    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
 
1793
      break;
 
1794
    case GST_STATE_CHANGE_PAUSED_TO_READY:
 
1795
      gst_qt_mux_reset (qtmux, TRUE);
 
1796
      break;
 
1797
    case GST_STATE_CHANGE_READY_TO_NULL:
 
1798
      break;
 
1799
    default:
 
1800
      break;
 
1801
  }
 
1802
 
 
1803
  return ret;
 
1804
}
 
1805
 
 
1806
 
 
1807
gboolean
 
1808
gst_qt_mux_register (GstPlugin * plugin)
 
1809
{
 
1810
  GTypeInfo typeinfo = {
 
1811
    sizeof (GstQTMuxClass),
 
1812
    (GBaseInitFunc) gst_qt_mux_base_init,
 
1813
    NULL,
 
1814
    (GClassInitFunc) gst_qt_mux_class_init,
 
1815
    NULL,
 
1816
    NULL,
 
1817
    sizeof (GstQTMux),
 
1818
    0,
 
1819
    (GInstanceInitFunc) gst_qt_mux_init,
 
1820
  };
 
1821
  static const GInterfaceInfo tag_setter_info = {
 
1822
    NULL, NULL, NULL
 
1823
  };
 
1824
  GType type;
 
1825
  GstQTMuxFormat format;
 
1826
  GstQTMuxClassParams *params;
 
1827
  guint i = 0;
 
1828
 
 
1829
  GST_LOG ("Registering muxers");
 
1830
 
 
1831
  while (TRUE) {
 
1832
    GstQTMuxFormatProp *prop;
 
1833
 
 
1834
    prop = &gst_qt_mux_format_list[i];
 
1835
    format = prop->format;
 
1836
    if (format == GST_QT_MUX_FORMAT_NONE)
 
1837
      break;
 
1838
 
 
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);
 
1845
 
 
1846
    /* create the type now */
 
1847
    type = g_type_register_static (GST_TYPE_ELEMENT, prop->type_name, &typeinfo,
 
1848
        0);
 
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);
 
1851
 
 
1852
    if (!gst_element_register (plugin, prop->name, GST_RANK_NONE, type))
 
1853
      return FALSE;
 
1854
 
 
1855
    i++;
 
1856
  }
 
1857
 
 
1858
  GST_LOG ("Finished registering muxers");
 
1859
 
 
1860
  return TRUE;
 
1861
}
 
1862
 
 
1863
gboolean
 
1864
gst_qt_mux_plugin_init (GstPlugin * plugin)
 
1865
{
 
1866
  GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
 
1867
 
 
1868
  return gst_qt_mux_register (plugin);
 
1869
}
 
1870
 
 
1871
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
 
1872
    GST_VERSION_MINOR,
 
1873
    "qtmux",
 
1874
    "Quicktime Muxer plugin",
 
1875
    gst_qt_mux_plugin_init, VERSION, "LGPL", "gsoc2008 package",
 
1876
    "embedded.ufcg.edu.br")