gstqtmux.c 108 KB
Newer Older
1
/* Quicktime muxer plugin for GStreamer
2
 * Copyright (C) 2008-2010 Thiago Santos <thiagoss@embedded.ufcg.edu.br>
3
 * Copyright (C) 2008 Mark Nauwelaerts <mnauw@users.sf.net>
4 5 6
 * Copyright (C) 2010 Nokia Corporation. All rights reserved.
 * Contact: Stefan Kost <stefan.kost@nokia.com>

7 8 9 10 11 12 13 14 15 16 17 18
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
19 20
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
21
 */
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
/*
 * Unless otherwise indicated, Source Code is licensed under MIT license.
 * See further explanation attached in License Statement (distributed in the file
 * LICENSE).
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
45 46 47


/**
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
48
 * SECTION:element-qtmux
49 50
 * @short_description: Muxer for quicktime(.mov) files
 *
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
 * This element merges streams (audio and video) into QuickTime(.mov) files.
 *
 * The following background intends to explain why various similar muxers
 * are present in this plugin.
 *
 * The <ulink url="http://www.apple.com/quicktime/resources/qtfileformat.pdf">
 * QuickTime file format specification</ulink> served as basis for the MP4 file
 * format specification (mp4mux), and as such the QuickTime file structure is
 * nearly identical to the so-called ISO Base Media file format defined in
 * ISO 14496-12 (except for some media specific parts).
 * In turn, the latter ISO Base Media format was further specialized as a
 * Motion JPEG-2000 file format in ISO 15444-3 (mj2mux)
 * and in various 3GPP(2) specs (gppmux).
 * The fragmented file features defined (only) in ISO Base Media are used by
 * ISMV files making up (a.o.) Smooth Streaming (ismlmux).
 *
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
 * A few properties (<link linkend="GstQTMux--movie-timescale">movie-timescale</link>,
 * <link linkend="GstQTMux--trak-timescale">trak-timescale</link>) allow adjusting
 * some technical parameters, which might be useful in (rare) cases to resolve
 * compatibility issues in some situations.
 *
 * Some other properties influence the result more fundamentally.
 * A typical mov/mp4 file's metadata (aka moov) is located at the end of the file,
 * somewhat contrary to this usually being called "the header".
 * However, a <link linkend="GstQTMux--faststart">faststart</link> file will
 * (with some effort) arrange this to be located near start of the file,
 * which then allows it e.g. to be played while downloading.
 * Alternatively, rather than having one chunk of metadata at start (or end),
 * there can be some metadata at start and most of the other data can be spread
 * out into fragments of <link linkend="GstQTMux--fragment-duration">fragment-duration</link>.
 * If such fragmented layout is intended for streaming purposes, then
 * <link linkend="GstQTMux--streamable">streamable</link> allows foregoing to add
 * index metadata (at the end of file).
 *
85 86
 * <refsect2>
 * <title>Example pipelines</title>
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
87
 * |[
88
 * gst-launch-1.0 v4l2src num-buffers=500 ! video/x-raw,width=320,height=240 ! videoconvert ! qtmux ! filesink location=video.mov
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
89
 * ]|
90 91 92
 * Records a video stream captured from a v4l2 device and muxes it into a qt file.
 * </refsect2>
 *
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
93
 * Last reviewed on 2010-12-03
94 95 96 97 98 99 100 101 102 103 104 105 106
 */

/*
 * Based on avimux
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <glib/gstdio.h>

#include <gst/gst.h>
107
#include <gst/base/gstcollectpads.h>
108
#include <gst/audio/audio.h>
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
109
#include <gst/video/video.h>
110
#include <gst/tag/tag.h>
111

Wim Taymans's avatar
Wim Taymans committed
112
#include <sys/types.h>
LRN's avatar
LRN committed
113 114 115 116 117 118 119 120
#ifdef G_OS_WIN32
#include <io.h>                 /* lseek, open, close, read */
#undef lseek
#define lseek _lseeki64
#undef off_t
#define off_t guint64
#endif

121 122 123 124
#ifdef _MSC_VER
#define ftruncate g_win32_ftruncate
#endif

Wim Taymans's avatar
Wim Taymans committed
125 126 127 128
#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif

129 130 131 132 133
#include "gstqtmux.h"

GST_DEBUG_CATEGORY_STATIC (gst_qt_mux_debug);
#define GST_CAT_DEFAULT gst_qt_mux_debug

134
#ifndef GST_REMOVE_DEPRECATED
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
enum
{
  DTS_METHOD_DD,
  DTS_METHOD_REORDER,
  DTS_METHOD_ASC
};

static GType
gst_qt_mux_dts_method_get_type (void)
{
  static GType gst_qt_mux_dts_method = 0;

  if (!gst_qt_mux_dts_method) {
    static const GEnumValue dts_methods[] = {
      {DTS_METHOD_DD, "delta/duration", "dd"},
      {DTS_METHOD_REORDER, "reorder", "reorder"},
      {DTS_METHOD_ASC, "ascending", "asc"},
      {0, NULL, NULL},
    };

    gst_qt_mux_dts_method =
        g_enum_register_static ("GstQTMuxDtsMethods", dts_methods);
  }

  return gst_qt_mux_dts_method;
}

#define GST_TYPE_QT_MUX_DTS_METHOD \
  (gst_qt_mux_dts_method_get_type ())
164
#endif
165

166 167 168 169 170 171 172 173 174 175 176
/* QTMux signals and args */
enum
{
  /* FILL ME */
  LAST_SIGNAL
};

enum
{
  PROP_0,
  PROP_MOVIE_TIMESCALE,
177
  PROP_TRAK_TIMESCALE,
178
  PROP_FAST_START,
179
  PROP_FAST_START_TEMP_FILE,
180
  PROP_MOOV_RECOV_FILE,
181
  PROP_FRAGMENT_DURATION,
182
  PROP_STREAMABLE,
183
#ifndef GST_REMOVE_DEPRECATED
184
  PROP_DTS_METHOD,
185
#endif
186
  PROP_DO_CTTS,
187 188
};

189 190
/* some spare for header size as well */
#define MDAT_LARGE_FILE_LIMIT           ((guint64) 1024 * 1024 * 1024 * 2)
Thiago Santos's avatar
Thiago Santos committed
191
#define MAX_TOLERATED_LATENESS          (GST_SECOND / 10)
192

193
#define DEFAULT_MOVIE_TIMESCALE         1000
194
#define DEFAULT_TRAK_TIMESCALE          0
195
#define DEFAULT_DO_CTTS                 TRUE
196 197
#define DEFAULT_FAST_START              FALSE
#define DEFAULT_FAST_START_TEMP_FILE    NULL
198
#define DEFAULT_MOOV_RECOV_FILE         NULL
199
#define DEFAULT_FRAGMENT_DURATION       0
200
#define DEFAULT_STREAMABLE              FALSE
201
#ifndef GST_REMOVE_DEPRECATED
202
#define DEFAULT_DTS_METHOD              DTS_METHOD_REORDER
203
#endif
204

205 206 207 208 209 210 211 212 213 214 215 216 217 218

static void gst_qt_mux_finalize (GObject * object);

static GstStateChangeReturn gst_qt_mux_change_state (GstElement * element,
    GstStateChange transition);

/* property functions */
static void gst_qt_mux_set_property (GObject * object,
    guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_qt_mux_get_property (GObject * object,
    guint prop_id, GValue * value, GParamSpec * pspec);

/* pad functions */
static GstPad *gst_qt_mux_request_new_pad (GstElement * element,
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
219
    GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
220 221 222
static void gst_qt_mux_release_pad (GstElement * element, GstPad * pad);

/* event */
223 224
static gboolean gst_qt_mux_sink_event (GstCollectPads * pads,
    GstCollectData * data, GstEvent * event, gpointer user_data);
225

226 227
static GstFlowReturn gst_qt_mux_handle_buffer (GstCollectPads * pads,
    GstCollectData * cdata, GstBuffer * buf, gpointer user_data);
228 229 230 231 232 233 234 235 236 237 238 239
static GstFlowReturn gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad,
    GstBuffer * buf);

static GstElementClass *parent_class = NULL;

static void
gst_qt_mux_base_init (gpointer g_class)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
  GstQTMuxClass *klass = (GstQTMuxClass *) g_class;
  GstQTMuxClassParams *params;
  GstPadTemplate *videosinktempl, *audiosinktempl, *srctempl;
240
  gchar *longname, *description;
241 242 243 244 245 246 247

  params =
      (GstQTMuxClassParams *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (g_class),
      GST_QT_MUX_PARAMS_QDATA);
  g_assert (params != NULL);

  /* construct the element details struct */
248
  longname = g_strdup_printf ("%s Muxer", params->prop->long_name);
249 250
  description = g_strdup_printf ("Multiplex audio and video into a %s file",
      params->prop->long_name);
251
  gst_element_class_set_static_metadata (element_class, longname,
252 253 254 255
      "Codec/Muxer", description,
      "Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>");
  g_free (longname);
  g_free (description);
256 257 258 259 260 261 262

  /* pad templates */
  srctempl = gst_pad_template_new ("src", GST_PAD_SRC,
      GST_PAD_ALWAYS, params->src_caps);
  gst_element_class_add_pad_template (element_class, srctempl);

  if (params->audio_sink_caps) {
263
    audiosinktempl = gst_pad_template_new ("audio_%u",
264 265 266 267 268
        GST_PAD_SINK, GST_PAD_REQUEST, params->audio_sink_caps);
    gst_element_class_add_pad_template (element_class, audiosinktempl);
  }

  if (params->video_sink_caps) {
269
    videosinktempl = gst_pad_template_new ("video_%u",
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
        GST_PAD_SINK, GST_PAD_REQUEST, params->video_sink_caps);
    gst_element_class_add_pad_template (element_class, videosinktempl);
  }

  klass->format = params->prop->format;
}

static void
gst_qt_mux_class_init (GstQTMuxClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;

  parent_class = g_type_class_peek_parent (klass);

  gobject_class->finalize = gst_qt_mux_finalize;
  gobject_class->get_property = gst_qt_mux_get_property;
  gobject_class->set_property = gst_qt_mux_set_property;

  g_object_class_install_property (gobject_class, PROP_MOVIE_TIMESCALE,
      g_param_spec_uint ("movie-timescale", "Movie timescale",
          "Timescale to use in the movie (units per second)",
          1, G_MAXUINT32, DEFAULT_MOVIE_TIMESCALE,
296
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
297 298 299 300 301
  g_object_class_install_property (gobject_class, PROP_TRAK_TIMESCALE,
      g_param_spec_uint ("trak-timescale", "Track timescale",
          "Timescale to use for the tracks (units per second, 0 is automatic)",
          0, G_MAXUINT32, DEFAULT_TRAK_TIMESCALE,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
302 303 304
  g_object_class_install_property (gobject_class, PROP_DO_CTTS,
      g_param_spec_boolean ("presentation-time",
          "Include presentation-time info",
305
          "Calculate and include presentation/composition time "
306
          "(in addition to decoding time)", DEFAULT_DO_CTTS,
307
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
308
#ifndef GST_REMOVE_DEPRECATED
309 310
  g_object_class_install_property (gobject_class, PROP_DTS_METHOD,
      g_param_spec_enum ("dts-method", "dts-method",
311
          "(DEPRECATED) Method to determine DTS time",
312
          GST_TYPE_QT_MUX_DTS_METHOD, DEFAULT_DTS_METHOD,
313 314 315
          G_PARAM_DEPRECATED | G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
          G_PARAM_STATIC_STRINGS));
#endif
316 317
  g_object_class_install_property (gobject_class, PROP_FAST_START,
      g_param_spec_boolean ("faststart", "Format file to faststart",
318
          "If the file should be formatted for faststart (headers first)",
319
          DEFAULT_FAST_START, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
320 321
  g_object_class_install_property (gobject_class, PROP_FAST_START_TEMP_FILE,
      g_param_spec_string ("faststart-file", "File to use for storing buffers",
322 323 324
          "File that will be used temporarily to store data from the stream "
          "when creating a faststart file. If null a filepath will be "
          "created automatically", DEFAULT_FAST_START_TEMP_FILE,
325
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
326
  g_object_class_install_property (gobject_class, PROP_MOOV_RECOV_FILE,
327 328 329
      g_param_spec_string ("moov-recovery-file",
          "File to store data for posterior moov atom recovery",
          "File to be used to store "
330 331
          "data for moov atom making movie file recovery possible in case "
          "of a crash during muxing. Null for disabled. (Experimental)",
332 333
          DEFAULT_MOOV_RECOV_FILE,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
334 335 336
  g_object_class_install_property (gobject_class, PROP_FRAGMENT_DURATION,
      g_param_spec_uint ("fragment-duration", "Fragment duration",
          "Fragment durations in ms (produce a fragmented file if > 0)",
337 338
          0, G_MAXUINT32, klass->format == GST_QT_MUX_FORMAT_ISML ?
          2000 : DEFAULT_FRAGMENT_DURATION,
339
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
340 341 342 343 344 345
  g_object_class_install_property (gobject_class, PROP_STREAMABLE,
      g_param_spec_boolean ("streamable", "Streamable",
          "If set to true, the output should be as if it is to be streamed "
          "and hence no indexes written or duration written.",
          DEFAULT_STREAMABLE,
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
346 347 348 349 350 351 352 353 354 355 356 357

  gstelement_class->request_new_pad =
      GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
  gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qt_mux_change_state);
  gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_release_pad);
}

static void
gst_qt_mux_pad_reset (GstQTPad * qtpad)
{
  qtpad->fourcc = 0;
  qtpad->is_out_of_order = FALSE;
358
  qtpad->have_dts = FALSE;
359 360 361
  qtpad->sample_size = 0;
  qtpad->sync = FALSE;
  qtpad->last_dts = 0;
Thiago Santos's avatar
Thiago Santos committed
362
  qtpad->first_ts = GST_CLOCK_TIME_NONE;
Thiago Santos's avatar
Thiago Santos committed
363
  qtpad->prepare_buf_func = NULL;
364 365
  qtpad->avg_bitrate = 0;
  qtpad->max_bitrate = 0;
366 367
  qtpad->total_duration = 0;
  qtpad->total_bytes = 0;
368 369 370

  qtpad->buf_head = 0;
  qtpad->buf_tail = 0;
371 372 373 374 375 376

  if (qtpad->last_buf)
    gst_buffer_replace (&qtpad->last_buf, NULL);

  /* reference owned elsewhere */
  qtpad->trak = NULL;
377 378 379 380 381 382

  if (qtpad->traf) {
    atom_traf_free (qtpad->traf);
    qtpad->traf = NULL;
  }
  atom_array_clear (&qtpad->fragment_buffers);
383 384 385

  /* reference owned elsewhere */
  qtpad->tfra = NULL;
386 387 388 389 390 391 392 393 394 395 396 397 398 399
}

/*
 * Takes GstQTMux back to its initial state
 */
static void
gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
{
  GSList *walk;

  qtmux->state = GST_QT_MUX_STATE_NONE;
  qtmux->header_size = 0;
  qtmux->mdat_size = 0;
  qtmux->mdat_pos = 0;
Thiago Santos's avatar
Thiago Santos committed
400
  qtmux->longest_chunk = GST_CLOCK_TIME_NONE;
401 402
  qtmux->video_pads = 0;
  qtmux->audio_pads = 0;
403
  qtmux->fragment_sequence = 0;
404 405 406 407 408 409 410 411 412

  if (qtmux->ftyp) {
    atom_ftyp_free (qtmux->ftyp);
    qtmux->ftyp = NULL;
  }
  if (qtmux->moov) {
    atom_moov_free (qtmux->moov);
    qtmux->moov = NULL;
  }
413 414 415 416
  if (qtmux->mfra) {
    atom_mfra_free (qtmux->mfra);
    qtmux->mfra = NULL;
  }
417 418
  if (qtmux->fast_start_file) {
    fclose (qtmux->fast_start_file);
419
    g_remove (qtmux->fast_start_file_path);
420 421
    qtmux->fast_start_file = NULL;
  }
422 423 424 425
  if (qtmux->moov_recov_file) {
    fclose (qtmux->moov_recov_file);
    qtmux->moov_recov_file = NULL;
  }
426 427 428
  for (walk = qtmux->extra_atoms; walk; walk = g_slist_next (walk)) {
    AtomInfo *ainfo = (AtomInfo *) walk->data;
    ainfo->free_func (ainfo->atom);
429
    g_free (ainfo);
430 431 432
  }
  g_slist_free (qtmux->extra_atoms);
  qtmux->extra_atoms = NULL;
433 434

  GST_OBJECT_LOCK (qtmux);
435
  gst_tag_setter_reset_tags (GST_TAG_SETTER (qtmux));
436
  GST_OBJECT_UNLOCK (qtmux);
437 438

  /* reset pad data */
439
  for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
440 441 442 443 444 445 446 447 448 449
    GstQTPad *qtpad = (GstQTPad *) walk->data;
    gst_qt_mux_pad_reset (qtpad);

    /* hm, moov_free above yanked the traks away from us,
     * so do not free, but do clear */
    qtpad->trak = NULL;
  }

  if (alloc) {
    qtmux->moov = atom_moov_new (qtmux->context);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
450
    /* ensure all is as nice and fresh as request_new_pad would provide it */
451
    for (walk = qtmux->sinkpads; walk; walk = g_slist_next (walk)) {
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
452 453
      GstQTPad *qtpad = (GstQTPad *) walk->data;

454 455
      qtpad->trak = atom_trak_new (qtmux->context);
      atom_moov_add_trak (qtmux->moov, qtpad->trak);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
456
    }
457 458 459 460 461 462 463 464 465 466 467 468 469 470
  }
}

static void
gst_qt_mux_init (GstQTMux * qtmux, GstQTMuxClass * qtmux_klass)
{
  GstElementClass *klass = GST_ELEMENT_CLASS (qtmux_klass);
  GstPadTemplate *templ;

  templ = gst_element_class_get_pad_template (klass, "src");
  qtmux->srcpad = gst_pad_new_from_template (templ, "src");
  gst_pad_use_fixed_caps (qtmux->srcpad);
  gst_element_add_pad (GST_ELEMENT (qtmux), qtmux->srcpad);

471
  qtmux->sinkpads = NULL;
472 473
  qtmux->collect = gst_collect_pads_new ();
  gst_collect_pads_set_buffer_function (qtmux->collect,
474
      GST_DEBUG_FUNCPTR (gst_qt_mux_handle_buffer), qtmux);
475
  gst_collect_pads_set_event_function (qtmux->collect,
476
      GST_DEBUG_FUNCPTR (gst_qt_mux_sink_event), qtmux);
477 478
  gst_collect_pads_set_clip_function (qtmux->collect,
      GST_DEBUG_FUNCPTR (gst_collect_pads_clip_running_time), qtmux);
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497

  /* properties set to default upon construction */

  /* always need this */
  qtmux->context =
      atoms_context_new (gst_qt_mux_map_format_to_flavor (qtmux_klass->format));

  /* internals to initial state */
  gst_qt_mux_reset (qtmux, TRUE);
}


static void
gst_qt_mux_finalize (GObject * object)
{
  GstQTMux *qtmux = GST_QT_MUX_CAST (object);

  gst_qt_mux_reset (qtmux, FALSE);

Thiago Santos's avatar
Thiago Santos committed
498
  g_free (qtmux->fast_start_file_path);
499
  g_free (qtmux->moov_recov_file_path);
500 501 502 503

  atoms_context_free (qtmux->context);
  gst_object_unref (qtmux->collect);

504 505
  g_slist_free (qtmux->sinkpads);

506 507 508
  G_OBJECT_CLASS (parent_class)->finalize (object);
}

Thiago Santos's avatar
Thiago Santos committed
509 510 511 512 513
static GstBuffer *
gst_qt_mux_prepare_jpc_buffer (GstQTPad * qtpad, GstBuffer * buf,
    GstQTMux * qtmux)
{
  GstBuffer *newbuf;
Wim Taymans's avatar
Wim Taymans committed
514
  GstMapInfo map;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
515
  gsize size;
Thiago Santos's avatar
Thiago Santos committed
516 517 518 519 520 521

  GST_LOG_OBJECT (qtmux, "Preparing jpc buffer");

  if (buf == NULL)
    return NULL;

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
522 523 524
  size = gst_buffer_get_size (buf);
  newbuf = gst_buffer_new_and_alloc (size + 8);
  gst_buffer_copy_into (newbuf, buf, GST_BUFFER_COPY_ALL, 8, size);
Thiago Santos's avatar
Thiago Santos committed
525

Wim Taymans's avatar
Wim Taymans committed
526 527 528
  gst_buffer_map (newbuf, &map, GST_MAP_WRITE);
  GST_WRITE_UINT32_BE (map.data, map.size);
  GST_WRITE_UINT32_LE (map.data + 4, FOURCC_jp2c);
Thiago Santos's avatar
Thiago Santos committed
529

Wim Taymans's avatar
Wim Taymans committed
530
  gst_buffer_unmap (buf, &map);
Thiago Santos's avatar
Thiago Santos committed
531 532 533 534 535
  gst_buffer_unref (buf);

  return newbuf;
}

536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
static void
gst_qt_mux_add_mp4_tag (GstQTMux * qtmux, const GstTagList * list,
    const char *tag, const char *tag2, guint32 fourcc)
{
  switch (gst_tag_get_type (tag)) {
      /* strings */
    case G_TYPE_STRING:
    {
      gchar *str = NULL;

      if (!gst_tag_list_get_string (list, tag, &str) || !str)
        break;
      GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
          GST_FOURCC_ARGS (fourcc), str);
      atom_moov_add_str_tag (qtmux->moov, fourcc, str);
      g_free (str);
      break;
    }
      /* double */
    case G_TYPE_DOUBLE:
    {
      gdouble value;

      if (!gst_tag_list_get_double (list, tag, &value))
        break;
      GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
          GST_FOURCC_ARGS (fourcc), (gint) value);
      atom_moov_add_uint_tag (qtmux->moov, fourcc, 21, (gint) value);
      break;
    }
    case G_TYPE_UINT:
    {
568
      guint value = 0;
569 570
      if (tag2) {
        /* paired unsigned integers */
571
        guint count = 0;
572
        gboolean got_tag;
573

574 575 576
        got_tag = gst_tag_list_get_uint (list, tag, &value);
        got_tag = gst_tag_list_get_uint (list, tag2, &count) || got_tag;
        if (!got_tag)
577 578 579 580 581 582 583 584 585 586 587 588 589
          break;
        GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u/%u",
            GST_FOURCC_ARGS (fourcc), value, count);
        atom_moov_add_uint_tag (qtmux->moov, fourcc, 0,
            value << 16 | (count & 0xFFFF));
      } else {
        /* unpaired unsigned integers */
        if (!gst_tag_list_get_uint (list, tag, &value))
          break;
        GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %u",
            GST_FOURCC_ARGS (fourcc), value);
        atom_moov_add_uint_tag (qtmux->moov, fourcc, 1, value);
      }
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
      break;
    }
    default:
      g_assert_not_reached ();
      break;
  }
}

static void
gst_qt_mux_add_mp4_date (GstQTMux * qtmux, const GstTagList * list,
    const char *tag, const char *tag2, guint32 fourcc)
{
  GDate *date = NULL;
  GDateYear year;
  GDateMonth month;
  GDateDay day;
  gchar *str;

608
  g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_DATE);
609 610 611 612 613 614 615 616

  if (!gst_tag_list_get_date (list, tag, &date) || !date)
    return;

  year = g_date_get_year (date);
  month = g_date_get_month (date);
  day = g_date_get_day (date);

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
617 618
  g_date_free (date);

619 620 621 622 623 624 625 626 627 628
  if (year == G_DATE_BAD_YEAR && month == G_DATE_BAD_MONTH &&
      day == G_DATE_BAD_DAY) {
    GST_WARNING_OBJECT (qtmux, "invalid date in tag");
    return;
  }

  str = g_strdup_printf ("%u-%u-%u", year, month, day);
  GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
      GST_FOURCC_ARGS (fourcc), str);
  atom_moov_add_str_tag (qtmux->moov, fourcc, str);
629
  g_free (str);
630 631 632 633 634 635 636 637
}

static void
gst_qt_mux_add_mp4_cover (GstQTMux * qtmux, const GstTagList * list,
    const char *tag, const char *tag2, guint32 fourcc)
{
  GValue value = { 0, };
  GstBuffer *buf;
638
  GstSample *sample;
639 640 641
  GstCaps *caps;
  GstStructure *structure;
  gint flags = 0;
Wim Taymans's avatar
Wim Taymans committed
642
  GstMapInfo map;
643

644
  g_return_if_fail (gst_tag_get_type (tag) == GST_TYPE_SAMPLE);
645 646 647 648

  if (!gst_tag_list_copy_value (&value, list, tag))
    return;

649 650 651 652 653 654
  sample = gst_value_get_sample (&value);

  if (!sample)
    goto done;

  buf = gst_sample_get_buffer (sample);
655 656 657
  if (!buf)
    goto done;

658
  caps = gst_sample_get_caps (sample);
659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676
  if (!caps) {
    GST_WARNING_OBJECT (qtmux, "preview image without caps");
    goto done;
  }

  GST_DEBUG_OBJECT (qtmux, "preview image caps %" GST_PTR_FORMAT, caps);

  structure = gst_caps_get_structure (caps, 0);
  if (gst_structure_has_name (structure, "image/jpeg"))
    flags = 13;
  else if (gst_structure_has_name (structure, "image/png"))
    flags = 14;

  if (!flags) {
    GST_WARNING_OBJECT (qtmux, "preview image format not supported");
    goto done;
  }

Wim Taymans's avatar
Wim Taymans committed
677
  gst_buffer_map (buf, &map, GST_MAP_READ);
678
  GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT
Wim Taymans's avatar
Wim Taymans committed
679 680 681
      " -> image size %" G_GSIZE_FORMAT "", GST_FOURCC_ARGS (fourcc), map.size);
  atom_moov_add_tag (qtmux->moov, fourcc, flags, map.data, map.size);
  gst_buffer_unmap (buf, &map);
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722
done:
  g_value_unset (&value);
}

static void
gst_qt_mux_add_3gp_str (GstQTMux * qtmux, const GstTagList * list,
    const char *tag, const char *tag2, guint32 fourcc)
{
  gchar *str = NULL;
  guint number;

  g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_STRING);
  g_return_if_fail (!tag2 || gst_tag_get_type (tag2) == G_TYPE_UINT);

  if (!gst_tag_list_get_string (list, tag, &str) || !str)
    return;

  if (tag2)
    if (!gst_tag_list_get_uint (list, tag2, &number))
      tag2 = NULL;

  if (!tag2) {
    GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
        GST_FOURCC_ARGS (fourcc), str);
    atom_moov_add_3gp_str_tag (qtmux->moov, fourcc, str);
  } else {
    GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s/%d",
        GST_FOURCC_ARGS (fourcc), str, number);
    atom_moov_add_3gp_str_int_tag (qtmux->moov, fourcc, str, number);
  }

  g_free (str);
}

static void
gst_qt_mux_add_3gp_date (GstQTMux * qtmux, const GstTagList * list,
    const char *tag, const char *tag2, guint32 fourcc)
{
  GDate *date = NULL;
  GDateYear year;

723
  g_return_if_fail (gst_tag_get_type (tag) == G_TYPE_DATE);
724 725 726 727 728 729 730 731 732 733 734

  if (!gst_tag_list_get_date (list, tag, &date) || !date)
    return;

  year = g_date_get_year (date);

  if (year == G_DATE_BAD_YEAR) {
    GST_WARNING_OBJECT (qtmux, "invalid date in tag");
    return;
  }

Edward Hervey's avatar
Edward Hervey committed
735 736
  GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %d",
      GST_FOURCC_ARGS (fourcc), year);
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
  atom_moov_add_3gp_uint_tag (qtmux->moov, fourcc, year);
}

static void
gst_qt_mux_add_3gp_location (GstQTMux * qtmux, const GstTagList * list,
    const char *tag, const char *tag2, guint32 fourcc)
{
  gdouble latitude = -360, longitude = -360, altitude = 0;
  gchar *location = NULL;
  guint8 *data, *ddata;
  gint size = 0, len = 0;
  gboolean ret = FALSE;

  g_return_if_fail (strcmp (tag, GST_TAG_GEO_LOCATION_NAME) == 0);

  ret = gst_tag_list_get_string (list, tag, &location);
  ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LONGITUDE,
      &longitude);
  ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_LATITUDE,
      &latitude);
  ret |= gst_tag_list_get_double (list, GST_TAG_GEO_LOCATION_ELEVATION,
      &altitude);

  if (!ret)
    return;

  if (location)
    len = strlen (location);
  size += len + 1 + 2;

  /* role + (long, lat, alt) + body + notes */
  size += 1 + 3 * 4 + 1 + 1;

  data = ddata = g_malloc (size);

  /* language tag */
  GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
  /* location */
  if (location)
    memcpy (data + 2, location, len);
  GST_WRITE_UINT8 (data + 2 + len, 0);
  data += len + 1 + 2;
  /* role */
  GST_WRITE_UINT8 (data, 0);
  /* long, lat, alt */
782 783 784 785
#define QT_WRITE_SFP32(data, fp) GST_WRITE_UINT32_BE(data, (guint32) ((gint) (fp * 65536.0)))
  QT_WRITE_SFP32 (data + 1, longitude);
  QT_WRITE_SFP32 (data + 5, latitude);
  QT_WRITE_SFP32 (data + 9, altitude);
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
  /* neither astronomical body nor notes */
  GST_WRITE_UINT16_BE (data + 13, 0);

  GST_DEBUG_OBJECT (qtmux, "Adding tag 'loci'");
  atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
  g_free (ddata);
}

static void
gst_qt_mux_add_3gp_keywords (GstQTMux * qtmux, const GstTagList * list,
    const char *tag, const char *tag2, guint32 fourcc)
{
  gchar *keywords = NULL;
  guint8 *data, *ddata;
  gint size = 0, i;
  gchar **kwds;

  g_return_if_fail (strcmp (tag, GST_TAG_KEYWORDS) == 0);

  if (!gst_tag_list_get_string (list, tag, &keywords) || !keywords)
    return;

  kwds = g_strsplit (keywords, ",", 0);
809
  g_free (keywords);
810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844

  size = 0;
  for (i = 0; kwds[i]; i++) {
    /* size byte + null-terminator */
    size += strlen (kwds[i]) + 1 + 1;
  }

  /* language tag + count + keywords */
  size += 2 + 1;

  data = ddata = g_malloc (size);

  /* language tag */
  GST_WRITE_UINT16_BE (data, language_code (GST_QT_MUX_DEFAULT_TAG_LANGUAGE));
  /* count */
  GST_WRITE_UINT8 (data + 2, i);
  data += 3;
  /* keywords */
  for (i = 0; kwds[i]; ++i) {
    gint len = strlen (kwds[i]);

    GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
        GST_FOURCC_ARGS (fourcc), kwds[i]);
    /* size */
    GST_WRITE_UINT8 (data, len + 1);
    memcpy (data + 1, kwds[i], len + 1);
    data += len + 2;
  }

  g_strfreev (kwds);

  atom_moov_add_3gp_tag (qtmux->moov, fourcc, ddata, size);
  g_free (ddata);
}

845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956
static gboolean
gst_qt_mux_parse_classification_string (GstQTMux * qtmux, const gchar * input,
    guint32 * p_fourcc, guint16 * p_table, gchar ** p_content)
{
  guint32 fourcc;
  gint table;
  gint size;
  const gchar *data;

  data = input;
  size = strlen (input);

  if (size < 4 + 3 + 1 + 1 + 1) {
    /* at least the minimum xxxx://y/z */
    GST_WARNING_OBJECT (qtmux, "Classification tag input (%s) too short, "
        "ignoring", input);
    return FALSE;
  }

  /* read the fourcc */
  memcpy (&fourcc, data, 4);
  size -= 4;
  data += 4;

  if (strncmp (data, "://", 3) != 0) {
    goto mismatch;
  }
  data += 3;
  size -= 3;

  /* read the table number */
  if (sscanf (data, "%d", &table) != 1) {
    goto mismatch;
  }
  if (table < 0) {
    GST_WARNING_OBJECT (qtmux, "Invalid table number in classification tag (%d)"
        ", table numbers should be positive, ignoring tag", table);
    return FALSE;
  }

  /* find the next / */
  while (size > 0 && data[0] != '/') {
    data += 1;
    size -= 1;
  }
  if (size == 0) {
    goto mismatch;
  }
  g_assert (data[0] == '/');

  /* skip the '/' */
  data += 1;
  size -= 1;
  if (size == 0) {
    goto mismatch;
  }

  /* read up the rest of the string */
  *p_content = g_strdup (data);
  *p_table = (guint16) table;
  *p_fourcc = fourcc;
  return TRUE;

mismatch:
  {
    GST_WARNING_OBJECT (qtmux, "Ignoring classification tag as "
        "input (%s) didn't match the expected entitycode://table/content",
        input);
    return FALSE;
  }
}

static void
gst_qt_mux_add_3gp_classification (GstQTMux * qtmux, const GstTagList * list,
    const char *tag, const char *tag2, guint32 fourcc)
{
  gchar *clsf_data = NULL;
  gint size = 0;
  guint32 entity = 0;
  guint16 table = 0;
  gchar *content = NULL;
  guint8 *data;

  g_return_if_fail (strcmp (tag, GST_TAG_3GP_CLASSIFICATION) == 0);

  if (!gst_tag_list_get_string (list, tag, &clsf_data) || !clsf_data)
    return;

  GST_DEBUG_OBJECT (qtmux, "Adding tag %" GST_FOURCC_FORMAT " -> %s",
      GST_FOURCC_ARGS (fourcc), clsf_data);

  /* parse the string, format is:
   * entityfourcc://table/content
   */
  gst_qt_mux_parse_classification_string (qtmux, clsf_data, &entity, &table,
      &content);
  g_free (clsf_data);
  /* +1 for the \0 */
  size = strlen (content) + 1;

  /* now we have everything, build the atom
   * atom description is at 3GPP TS 26.244 V8.2.0 (2009-09) */
  data = g_malloc (4 + 2 + 2 + size);
  GST_WRITE_UINT32_LE (data, entity);
  GST_WRITE_UINT16_BE (data + 4, (guint16) table);
  GST_WRITE_UINT16_BE (data + 6, 0);
  memcpy (data + 8, content, size);
  g_free (content);

  atom_moov_add_3gp_tag (qtmux->moov, fourcc, data, 4 + 2 + 2 + size);
  g_free (data);
}
957 958 959

typedef void (*GstQTMuxAddTagFunc) (GstQTMux * mux, const GstTagList * list,
    const char *tag, const char *tag2, guint32 fourcc);
960 961 962 963 964 965 966 967 968

/*
 * Struct to record mappings from gstreamer tags to fourcc codes
 */
typedef struct _GstTagToFourcc
{
  guint32 fourcc;
  const gchar *gsttag;
  const gchar *gsttag2;
969
  const GstQTMuxAddTagFunc func;
970 971 972
} GstTagToFourcc;

/* tag list tags to fourcc matching */
973 974
static const GstTagToFourcc tag_matches_mp4[] = {
  {FOURCC__alb, GST_TAG_ALBUM, NULL, gst_qt_mux_add_mp4_tag},
Thiago Santos's avatar
Thiago Santos committed
975
  {FOURCC_soal, GST_TAG_ALBUM_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
976
  {FOURCC__ART, GST_TAG_ARTIST, NULL, gst_qt_mux_add_mp4_tag},
Thiago Santos's avatar
Thiago Santos committed
977 978 979
  {FOURCC_soar, GST_TAG_ARTIST_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC_aART, GST_TAG_ALBUM_ARTIST, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC_soaa, GST_TAG_ALBUM_ARTIST_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
980 981
  {FOURCC__cmt, GST_TAG_COMMENT, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC__wrt, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_mp4_tag},
Thiago Santos's avatar
Thiago Santos committed
982 983 984 985 986
  {FOURCC_soco, GST_TAG_COMPOSER_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC_tvsh, GST_TAG_SHOW_NAME, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC_sosn, GST_TAG_SHOW_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC_tvsn, GST_TAG_SHOW_SEASON_NUMBER, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC_tves, GST_TAG_SHOW_EPISODE_NUMBER, NULL, gst_qt_mux_add_mp4_tag},
987 988
  {FOURCC__gen, GST_TAG_GENRE, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC__nam, GST_TAG_TITLE, NULL, gst_qt_mux_add_mp4_tag},
Thiago Santos's avatar
Thiago Santos committed
989 990 991
  {FOURCC_sonm, GST_TAG_TITLE_SORTNAME, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC_perf, GST_TAG_PERFORMER, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC__grp, GST_TAG_GROUPING, NULL, gst_qt_mux_add_mp4_tag},
992
  {FOURCC__des, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_mp4_tag},
Thiago Santos's avatar
Thiago Santos committed
993
  {FOURCC__lyr, GST_TAG_LYRICS, NULL, gst_qt_mux_add_mp4_tag},
994 995 996 997 998 999 1000 1001 1002 1003
  {FOURCC__too, GST_TAG_ENCODER, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC_keyw, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC__day, GST_TAG_DATE, NULL, gst_qt_mux_add_mp4_date},
  {FOURCC_tmpo, GST_TAG_BEATS_PER_MINUTE, NULL, gst_qt_mux_add_mp4_tag},
  {FOURCC_trkn, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT,
      gst_qt_mux_add_mp4_tag},
  {FOURCC_disk, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT,
      gst_qt_mux_add_mp4_tag},
  {FOURCC_covr, GST_TAG_PREVIEW_IMAGE, NULL, gst_qt_mux_add_mp4_cover},
1004
  {FOURCC_covr, GST_TAG_IMAGE, NULL, gst_qt_mux_add_mp4_cover},
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018
  {0, NULL,}
};

static const GstTagToFourcc tag_matches_3gp[] = {
  {FOURCC_titl, GST_TAG_TITLE, NULL, gst_qt_mux_add_3gp_str},
  {FOURCC_dscp, GST_TAG_DESCRIPTION, NULL, gst_qt_mux_add_3gp_str},
  {FOURCC_cprt, GST_TAG_COPYRIGHT, NULL, gst_qt_mux_add_3gp_str},
  {FOURCC_perf, GST_TAG_ARTIST, NULL, gst_qt_mux_add_3gp_str},
  {FOURCC_auth, GST_TAG_COMPOSER, NULL, gst_qt_mux_add_3gp_str},
  {FOURCC_gnre, GST_TAG_GENRE, NULL, gst_qt_mux_add_3gp_str},
  {FOURCC_kywd, GST_TAG_KEYWORDS, NULL, gst_qt_mux_add_3gp_keywords},
  {FOURCC_yrrc, GST_TAG_DATE, NULL, gst_qt_mux_add_3gp_date},
  {FOURCC_albm, GST_TAG_ALBUM, GST_TAG_TRACK_NUMBER, gst_qt_mux_add_3gp_str},
  {FOURCC_loci, GST_TAG_GEO_LOCATION_NAME, NULL, gst_qt_mux_add_3gp_location},
1019 1020
  {FOURCC_clsf, GST_TAG_3GP_CLASSIFICATION, NULL,
      gst_qt_mux_add_3gp_classification},
1021 1022 1023 1024 1025 1026
  {0, NULL,}
};

/* qtdemux produces these for atoms it cannot parse */
#define GST_QT_DEMUX_PRIVATE_TAG "private-qt-tag"

Thiago Santos's avatar
Thiago Santos committed
1027 1028 1029 1030
static void
gst_qt_mux_add_xmp_tags (GstQTMux * qtmux, const GstTagList * list)
{
  GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1031
  GstBuffer *xmp = NULL;
Thiago Santos's avatar
Thiago Santos committed
1032

1033 1034 1035
  /* adobe specs only have 'quicktime' and 'mp4',
   * but I guess we can extrapolate to gpp.
   * Keep mj2 out for now as we don't add any tags for it yet.
1036 1037 1038
   * If you have further info about xmp on these formats, please share */
  if (qtmux_klass->format == GST_QT_MUX_FORMAT_MJ2)
    return;
Thiago Santos's avatar
Thiago Santos committed
1039 1040 1041

  GST_DEBUG_OBJECT (qtmux, "Adding xmp tags");

1042
  if (qtmux_klass->format == GST_QT_MUX_FORMAT_QT) {
1043 1044 1045 1046
    xmp = gst_tag_xmp_writer_tag_list_to_xmp_buffer (GST_TAG_XMP_WRITER (qtmux),
        list, TRUE);
    if (xmp)
      atom_moov_add_xmp_tags (qtmux->moov, xmp);
1047
  } else {
1048
    AtomInfo *ainfo;
1049
    /* for isom/mp4, it is a top level uuid atom */
1050 1051 1052 1053 1054 1055 1056
    xmp = gst_tag_xmp_writer_tag_list_to_xmp_buffer (GST_TAG_XMP_WRITER (qtmux),
        list, TRUE);
    if (xmp) {
      ainfo = build_uuid_xmp_atom (xmp);
      if (ainfo) {
        qtmux->extra_atoms = g_slist_prepend (qtmux->extra_atoms, ainfo);
      }
1057 1058
    }
  }
1059 1060
  if (xmp)
    gst_buffer_unref (xmp);
Thiago Santos's avatar
Thiago Santos committed
1061 1062
}

1063 1064 1065
static void
gst_qt_mux_add_metadata_tags (GstQTMux * qtmux, const GstTagList * list)
{
1066
  GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
1067 1068 1069
  guint32 fourcc;
  gint i;
  const gchar *tag, *tag2;
1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
  const GstTagToFourcc *tag_matches;

  switch (qtmux_klass->format) {
    case GST_QT_MUX_FORMAT_3GP:
      tag_matches = tag_matches_3gp;
      break;
    case GST_QT_MUX_FORMAT_MJ2:
      tag_matches = NULL;
      break;
    default:
      /* sort of iTunes style for mp4 and QT (?) */
      tag_matches = tag_matches_mp4;
      break;
  }

  if (!tag_matches)
    return;
1087 1088 1089 1090 1091 1092

  for (i = 0; tag_matches[i].fourcc; i++) {
    fourcc = tag_matches[i].fourcc;
    tag = tag_matches[i].gsttag;
    tag2 = tag_matches[i].gsttag2;

1093 1094
    g_assert (tag_matches[i].func);
    tag_matches[i].func (qtmux, list, tag, tag2, fourcc);
1095 1096 1097 1098 1099 1100 1101 1102
  }

  /* add unparsed blobs if present */
  if (gst_tag_exists (GST_QT_DEMUX_PRIVATE_TAG)) {
    guint num_tags;

    num_tags = gst_tag_list_get_tag_size (list, GST_QT_DEMUX_PRIVATE_TAG);
    for (i = 0; i < num_tags; ++i) {
1103
      GstSample *sample = NULL;
1104
      GstBuffer *buf;
1105
      const GstStructure *s;
1106

1107 1108 1109 1110
      if (!gst_tag_list_get_sample_index (list, GST_QT_DEMUX_PRIVATE_TAG, i,
              &sample))
        continue;
      buf = gst_sample_get_buffer (sample);
1111

1112
      if (buf && (s = gst_sample_get_info (sample))) {
1113
        const gchar *style = NULL;
Wim Taymans's avatar
Wim Taymans committed
1114
        GstMapInfo map;
1115

Wim Taymans's avatar
Wim Taymans committed
1116
        gst_buffer_map (buf, &map, GST_MAP_READ);
1117
        GST_DEBUG_OBJECT (qtmux,
1118 1119
            "Found private tag %d/%d; size %" G_GSIZE_FORMAT ", info %"
            GST_PTR_FORMAT, i, num_tags, map.size, s);
1120
        if (s && (style = gst_structure_get_string (s, "style"))) {
1121 1122 1123 1124 1125 1126
          /* try to prevent some style tag ending up into another variant
           * (todo: make into a list if more cases) */
          if ((strcmp (style, "itunes") == 0 &&
                  qtmux_klass->format == GST_QT_MUX_FORMAT_MP4) ||
              (strcmp (style, "iso") == 0 &&
                  qtmux_klass->format == GST_QT_MUX_FORMAT_3GP)) {
1127
            GST_DEBUG_OBJECT (qtmux, "Adding private tag");
Wim Taymans's avatar
Wim Taymans committed
1128
            atom_moov_add_blob_tag (qtmux->moov, map.data, map.size);
1129 1130
          }
        }
Wim Taymans's avatar
Wim Taymans committed
1131
        gst_buffer_unmap (buf, &map);
1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145
      }
    }
  }

  return;
}

/*
 * Gets the tagsetter iface taglist and puts the known tags
 * into the output stream
 */
static void
gst_qt_mux_setup_metadata (GstQTMux * qtmux)
{
1146
  const GstTagList *tags;
1147

1148
  GST_OBJECT_LOCK (qtmux);
1149
  tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (qtmux));
1150
  GST_OBJECT_UNLOCK (qtmux);
1151

1152
  GST_LOG_OBJECT (qtmux, "tags: %" GST_PTR_FORMAT, tags);
1153

1154
  if (tags && !gst_tag_list_is_empty (tags)) {
1155 1156 1157 1158 1159 1160 1161
    GstTagList *copy = gst_tag_list_copy (tags);

    GST_DEBUG_OBJECT (qtmux, "Removing bogus tags");
    gst_tag_list_remove_tag (copy, GST_TAG_VIDEO_CODEC);
    gst_tag_list_remove_tag (copy, GST_TAG_AUDIO_CODEC);
    gst_tag_list_remove_tag (copy, GST_TAG_CONTAINER_FORMAT);

1162
    GST_DEBUG_OBJECT (qtmux, "Formatting tags");
1163 1164
    gst_qt_mux_add_metadata_tags (qtmux, copy);
    gst_qt_mux_add_xmp_tags (qtmux, copy);
1165
    gst_tag_list_unref (copy);
1166
  } else {
1167
    GST_DEBUG_OBJECT (qtmux, "No tags received");
1168 1169 1170
  }
}

1171 1172 1173 1174 1175 1176
static inline GstBuffer *
_gst_buffer_new_take_data (guint8 * data, guint size)
{
  GstBuffer *buf;

  buf = gst_buffer_new ();
Wim Taymans's avatar
Wim Taymans committed
1177
  gst_buffer_append_memory (buf,
Wim Taymans's avatar
Wim Taymans committed
1178
      gst_memory_new_wrapped (0, data, size, 0, size, data, g_free));
1179 1180 1181 1182

  return buf;
}

1183 1184 1185 1186 1187
static GstFlowReturn
gst_qt_mux_send_buffer (GstQTMux * qtmux, GstBuffer * buf, guint64 * offset,
    gboolean mind_fast)
{
  GstFlowReturn res;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
1188
  gsize size;
1189 1190 1191

  g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
1192
  size = gst_buffer_get_size (buf);
1193
  GST_LOG_OBJECT (qtmux, "sending buffer size %" G_GSIZE_FORMAT, size);
1194 1195

  if (mind_fast && qtmux->fast_start_file) {
Wim Taymans's avatar
Wim Taymans committed
1196
    GstMapInfo map;
1197 1198 1199
    gint ret;

    GST_LOG_OBJECT (qtmux, "to temporary file");
Wim Taymans's avatar
Wim Taymans committed
1200 1201 1202
    gst_buffer_map (buf, &map, GST_MAP_READ);
    ret = fwrite (map.data, sizeof (guint8), map.size, qtmux->fast_start_file);
    gst_buffer_unmap (buf, &map);
1203 1204 1205 1206 1207 1208 1209 1210 1211 1212
    gst_buffer_unref (buf);
    if (ret != size)
      goto write_error;
    else
      res = GST_FLOW_OK;
  } else {
    GST_LOG_OBJECT (qtmux, "downstream");
    res = gst_pad_push (qtmux->srcpad, buf);
  }

1213
  if (G_LIKELY (offset))
1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226
    *offset += size;

  return res;

  /* ERRORS */
write_error:
  {
    GST_ELEMENT_ERROR (qtmux, RESOURCE, WRITE,
        ("Failed to write to temporary file"), GST_ERROR_SYSTEM);
    return GST_FLOW_ERROR;
  }
}

1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242
static gboolean
gst_qt_mux_seek_to_beginning (FILE * f)
{
#ifdef HAVE_FSEEKO
  if (fseeko (f, (off_t) 0, SEEK_SET) != 0)
    return FALSE;
#elif defined (G_OS_UNIX) || defined (G_OS_WIN32)
  if (lseek (fileno (f), (off_t) 0, SEEK_SET) == (off_t) - 1)
    return FALSE;
#else
  if (fseek (f, (long) 0, SEEK_SET) != 0)
    return FALSE;
#endif
  return TRUE;
}

1243 1244 1245 1246 1247 1248 1249 1250 1251
static GstFlowReturn
gst_qt_mux_send_buffered_data (GstQTMux * qtmux, guint64 * offset)
{
  GstFlowReturn ret = GST_FLOW_OK;
  GstBuffer *buf = NULL;

  if (fflush (qtmux->fast_start_file))
    goto flush_failed;

1252
  if (!gst_qt_mux_seek_to_beginning (qtmux->fast_start_file))
LRN's avatar
LRN committed
1253
    goto seek_failed;
1254 1255 1256 1257 1258 1259 1260

  /* hm, this could all take a really really long time,
   * but there may not be another way to get moov atom first
   * (somehow optimize copy?) */
  GST_DEBUG_OBJECT (qtmux, "Sending buffered data");
  while (ret == GST_FLOW_OK) {
    const int bufsize = 4096;
Wim Taymans's avatar
Wim Taymans committed
1261
    GstMapInfo map;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
1262
    gsize size;
1263 1264

    buf = gst_buffer_new_and_alloc (bufsize);
Wim Taymans's avatar
Wim Taymans committed
1265 1266
    gst_buffer_map (buf, &map, GST_MAP_WRITE);
    size = fread (map.data, sizeof (guint8), bufsize, qtmux->fast_start_file);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
1267
    if (size == 0) {
Wim Taymans's avatar
Wim Taymans committed
1268
      gst_buffer_unmap (buf, &map);
1269
      break;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
1270
    }
1271
    GST_LOG_OBJECT (qtmux, "Pushing buffered buffer of size %d", (gint) size);
Wim Taymans's avatar
Wim Taymans committed
1272
    gst_buffer_unmap (buf, &map);
1273 1274
    if (size != bufsize)
      gst_buffer_set_size (buf, size);
1275 1276 1277 1278 1279 1280
    ret = gst_qt_mux_send_buffer (qtmux, buf, offset, FALSE);
    buf = NULL;
  }
  if (buf)
    gst_buffer_unref (buf);

1281 1282 1283 1284 1285
  if (ftruncate (fileno (qtmux->fast_start_file), 0))
    goto seek_failed;
  if (!gst_qt_mux_seek_to_beginning (qtmux->fast_start_file))
    goto seek_failed;