matroska-mux.c 85.5 KB
Newer Older
1 2
/* GStreamer Matroska muxer/demuxer
 * (c) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
3
 * (c) 2005 Michal Benes <michal.benes@xeris.cz>
4
 * (c) 2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *
 * matroska-mux.c: matroska file/stream muxer
 *
 * 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
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

24 25 26 27
/* TODO: - check everywhere that we don't write invalid values
 *       - make sure timestamps are correctly scaled everywhere
 */

28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
/**
 * SECTION:element-matroskamux
 *
 * matroskamux muxes different input streams into a Matroska file.
 *
 * <refsect2>
 * <title>Example launch line</title>
 * |[
 * gst-launch -v filesrc location=/path/to/mp3 ! mp3parse ! matroskamux name=mux ! filesink location=test.mkv  filesrc location=/path/to/theora.ogg ! oggdemux ! theoraparse ! mux.
 * ]| This pipeline muxes an MP3 file and a Ogg Theora video into a Matroska file.
 * |[
 * gst-launch -v audiotestsrc num-buffers=100 ! audioconvert ! vorbisenc ! matroskamux ! filesink location=test.mka
 * ]| This pipeline muxes a 440Hz sine wave encoded with the Vorbis codec into a Matroska file.
 * </refsect2>
 */

44 45 46 47 48 49 50
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <math.h>
#include <string.h>

51
#include <gst/riff/riff-media.h>
52
#include <gst/tag/tag.h>
53

54 55 56
#include "matroska-mux.h"
#include "matroska-ids.h"

57
GST_DEBUG_CATEGORY_STATIC (matroskamux_debug);
58 59
#define GST_CAT_DEFAULT matroskamux_debug

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
60 61
enum
{
62
  ARG_0,
63
  ARG_WRITING_APP,
64 65
  ARG_MATROSKA_VERSION,
  ARG_MIN_INDEX_INTERVAL
66 67
};

68 69
#define  DEFAULT_MATROSKA_VERSION        1
#define  DEFAULT_WRITING_APP             "GStreamer Matroska muxer"
70
#define  DEFAULT_MIN_INDEX_INTERVAL      0
71

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
72 73 74 75 76
static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-matroska")
    );
77

David Schleef's avatar
David Schleef committed
78 79 80
#define COMMON_VIDEO_CAPS \
  "width = (int) [ 16, 4096 ], " \
  "height = (int) [ 16, 4096 ], " \
81
  "framerate = (fraction) [ 0, MAX ]"
82

83 84 85 86
#define COMMON_VIDEO_CAPS_NO_FRAMERATE \
  "width = (int) [ 16, 4096 ], " \
  "height = (int) [ 16, 4096 ] "

87 88 89 90
/* FIXME: 
 * * require codec data, etc as needed
 */

David Schleef's avatar
David Schleef committed
91
static GstStaticPadTemplate videosink_templ =
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
92 93 94 95
    GST_STATIC_PAD_TEMPLATE ("video_%d",
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS ("video/mpeg, "
96 97 98
        "mpegversion = (int) { 1, 2, 4 }, "
        "systemstream = (boolean) false, "
        COMMON_VIDEO_CAPS "; "
99 100
        "video/x-h264, "
        COMMON_VIDEO_CAPS "; "
101 102 103 104
        "video/x-divx, "
        COMMON_VIDEO_CAPS "; "
        "video/x-xvid, "
        COMMON_VIDEO_CAPS "; "
105 106 107 108 109 110
        "video/x-huffyuv, "
        COMMON_VIDEO_CAPS "; "
        "video/x-dv, "
        COMMON_VIDEO_CAPS "; "
        "video/x-h263, "
        COMMON_VIDEO_CAPS "; "
111 112
        "video/x-msmpeg, "
        COMMON_VIDEO_CAPS "; "
113
        "image/jpeg, "
114
        COMMON_VIDEO_CAPS_NO_FRAMERATE "; "
115
        "video/x-theora; "
116 117
        "video/x-dirac, "
        COMMON_VIDEO_CAPS "; "
118 119 120
        "video/x-pn-realvideo, "
        "rmversion = (int) [1, 4], "
        COMMON_VIDEO_CAPS "; "
121
        "video/x-raw-yuv, "
122
        "format = (fourcc) { YUY2, I420, YV12, UYVY, AYUV }, "
123 124
        COMMON_VIDEO_CAPS "; "
        "video/x-wmv, " "wmvversion = (int) [ 1, 3 ], " COMMON_VIDEO_CAPS)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
125
    );
David Schleef's avatar
David Schleef committed
126 127

#define COMMON_AUDIO_CAPS \
128 129
  "channels = (int) [ 1, MAX ], " \
  "rate = (int) [ 1, MAX ]"
130 131

/* FIXME:
132
 * * require codec data, etc as needed
133
 */
David Schleef's avatar
David Schleef committed
134
static GstStaticPadTemplate audiosink_templ =
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
135 136 137 138
    GST_STATIC_PAD_TEMPLATE ("audio_%d",
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS ("audio/mpeg, "
139 140
        "mpegversion = (int) 1, "
        "layer = (int) [ 1, 3 ], "
141
        "stream-format = (string) { raw }, "
142 143 144 145 146 147
        COMMON_AUDIO_CAPS "; "
        "audio/mpeg, "
        "mpegversion = (int) { 2, 4 }, "
        COMMON_AUDIO_CAPS "; "
        "audio/x-ac3, "
        COMMON_AUDIO_CAPS "; "
148 149
        "audio/x-vorbis, "
        COMMON_AUDIO_CAPS "; "
150 151
        "audio/x-flac, "
        COMMON_AUDIO_CAPS "; "
152 153
        "audio/x-speex, "
        COMMON_AUDIO_CAPS "; "
154
        "audio/x-raw-int, "
155 156 157 158 159 160 161
        "width = (int) 8, "
        "depth = (int) 8, "
        "signed = (boolean) false, "
        COMMON_AUDIO_CAPS ";"
        "audio/x-raw-int, "
        "width = (int) 16, "
        "depth = (int) 16, "
162
        "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, "
163
        "signed = (boolean) true, "
164
        COMMON_AUDIO_CAPS ";"
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
        "audio/x-raw-int, "
        "width = (int) 24, "
        "depth = (int) 24, "
        "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, "
        "signed = (boolean) true, "
        COMMON_AUDIO_CAPS ";"
        "audio/x-raw-int, "
        "width = (int) 32, "
        "depth = (int) 32, "
        "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, "
        "signed = (boolean) true, "
        COMMON_AUDIO_CAPS ";"
        "audio/x-raw-float, "
        "width = (int) [ 32, 64 ], "
        "endianness = (int) LITTLE_ENDIAN, "
        COMMON_AUDIO_CAPS ";"
181
        "audio/x-tta, "
182
        "width = (int) { 8, 16, 24 }, "
183 184
        "channels = (int) { 1, 2 }, " "rate = (int) [ 8000, 96000 ]; "
        "audio/x-pn-realaudio, "
185 186 187 188
        "raversion = (int) { 1, 2, 8 }, " COMMON_AUDIO_CAPS "; "
        "audio/x-wma, " "wmaversion = (int) [ 1, 3 ], "
        "block_align = (int) [ 0, 65535 ], bitrate = (int) [ 0, 524288 ], "
        COMMON_AUDIO_CAPS)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
189
    );
David Schleef's avatar
David Schleef committed
190 191

static GstStaticPadTemplate subtitlesink_templ =
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
192 193 194 195
GST_STATIC_PAD_TEMPLATE ("subtitle_%d",
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS_ANY);
196

197
static GArray *used_uids;
198
G_LOCK_DEFINE_STATIC (used_uids);
199

200 201
static void gst_matroska_mux_add_interfaces (GType type);

202
GType gst_matroska_mux_get_type (void);
203 204
GST_BOILERPLATE_FULL (GstMatroskaMux, gst_matroska_mux, GstElement,
    GST_TYPE_ELEMENT, gst_matroska_mux_add_interfaces);
205 206 207

/* Matroska muxer destructor */
static void gst_matroska_mux_finalize (GObject * object);
208

209 210 211
/* Pads collected callback */
static GstFlowReturn
gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data);
212 213

/* pad functions */
214 215
static gboolean gst_matroska_mux_handle_src_event (GstPad * pad,
    GstEvent * event);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
216 217
static GstPad *gst_matroska_mux_request_new_pad (GstElement * element,
    GstPadTemplate * templ, const gchar * name);
218
static void gst_matroska_mux_release_pad (GstElement * element, GstPad * pad);
219 220

/* gst internal change state handler */
221 222
static GstStateChangeReturn
gst_matroska_mux_change_state (GstElement * element, GstStateChange transition);
223 224

/* gobject bla bla */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
225 226 227 228
static void gst_matroska_mux_set_property (GObject * object,
    guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_matroska_mux_get_property (GObject * object,
    guint prop_id, GValue * value, GParamSpec * pspec);
229 230

/* reset muxer */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
231
static void gst_matroska_mux_reset (GstElement * element);
232

233
/* uid generation */
234
static guint64 gst_matroska_mux_create_uid ();
235

236 237 238 239
static gboolean theora_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context);
static gboolean vorbis_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context);
240 241
static gboolean speex_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context);
242 243
static gboolean kate_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context);
244 245
static gboolean flac_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context);
246

247 248 249 250 251 252 253 254
static void
gst_matroska_mux_add_interfaces (GType type)
{
  static const GInterfaceInfo tag_setter_info = { NULL, NULL, NULL };

  g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info);
}

255
static void
256
gst_matroska_mux_base_init (gpointer g_class)
257
{
258
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
259 260

  gst_element_class_add_pad_template (element_class,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
261
      gst_static_pad_template_get (&videosink_templ));
262
  gst_element_class_add_pad_template (element_class,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
263
      gst_static_pad_template_get (&audiosink_templ));
264
  gst_element_class_add_pad_template (element_class,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
265
      gst_static_pad_template_get (&subtitlesink_templ));
266
  gst_element_class_add_pad_template (element_class,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
267
      gst_static_pad_template_get (&src_templ));
268 269 270
  gst_element_class_set_details_simple (element_class, "Matroska muxer",
      "Codec/Muxer",
      "Muxes video/audio/subtitle streams into a matroska stream",
271
      "GStreamer maintainers <gstreamer-devel@lists.sourceforge.net>");
272 273 274

  GST_DEBUG_CATEGORY_INIT (matroskamux_debug, "matroskamux", 0,
      "Matroska muxer");
275 276 277
}

static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
278
gst_matroska_mux_class_init (GstMatroskaMuxClass * klass)
279 280 281 282 283 284 285
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

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

286
  gobject_class->finalize = gst_matroska_mux_finalize;
287 288 289 290

  gobject_class->get_property = gst_matroska_mux_get_property;
  gobject_class->set_property = gst_matroska_mux_set_property;

291 292 293 294
  g_object_class_install_property (gobject_class, ARG_WRITING_APP,
      g_param_spec_string ("writing-app", "Writing application.",
          "The name the application that creates the matroska file.",
          NULL, G_PARAM_READWRITE));
295 296 297
  g_object_class_install_property (gobject_class, ARG_MATROSKA_VERSION,
      g_param_spec_int ("version", "Matroska version",
          "This parameter determines what matroska features can be used.",
298
          1, 2, DEFAULT_MATROSKA_VERSION, G_PARAM_READWRITE));
299 300 301 302
  g_object_class_install_property (gobject_class, ARG_MIN_INDEX_INTERVAL,
      g_param_spec_int64 ("min-index-interval", "Minimum time between index "
          "entries", "An index entry is created every so many nanoseconds.",
          0, G_MAXINT64, DEFAULT_MIN_INDEX_INTERVAL, G_PARAM_READWRITE));
303

304 305 306 307 308 309
  gstelement_class->change_state =
      GST_DEBUG_FUNCPTR (gst_matroska_mux_change_state);
  gstelement_class->request_new_pad =
      GST_DEBUG_FUNCPTR (gst_matroska_mux_request_new_pad);
  gstelement_class->release_pad =
      GST_DEBUG_FUNCPTR (gst_matroska_mux_release_pad);
310 311
}

312 313 314 315 316 317 318 319

/**
 * gst_matroska_mux_init:
 * @mux: #GstMatroskaMux that should be initialized.
 * @g_class: Class of the muxer.
 *
 * Matroska muxer constructor.
 */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
320
static void
321
gst_matroska_mux_init (GstMatroskaMux * mux, GstMatroskaMuxClass * g_class)
322
{
323
  mux->srcpad = gst_pad_new_from_static_template (&src_templ, "src");
324
  gst_pad_set_event_function (mux->srcpad, gst_matroska_mux_handle_src_event);
325 326
  gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad);

327 328
  mux->collect = gst_collect_pads_new ();
  gst_collect_pads_set_function (mux->collect,
329 330
      (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_matroska_mux_collected),
      mux);
331

332 333
  mux->ebml_write = gst_ebml_write_new (mux->srcpad);

334 335 336
  /* property defaults */
  mux->matroska_version = DEFAULT_MATROSKA_VERSION;
  mux->writing_app = g_strdup (DEFAULT_WRITING_APP);
337
  mux->min_index_interval = DEFAULT_MIN_INDEX_INTERVAL;
338

339
  /* initialize internal variables */
340
  mux->index = NULL;
341 342 343 344
  mux->num_streams = 0;
  mux->num_a_streams = 0;
  mux->num_t_streams = 0;
  mux->num_v_streams = 0;
345

346
  /* initialize remaining variables */
347 348 349
  gst_matroska_mux_reset (GST_ELEMENT (mux));
}

350 351 352 353 354 355 356 357 358 359 360 361 362 363

/**
 * gst_matroska_mux_finalize:
 * @object: #GstMatroskaMux that should be finalized.
 *
 * Finalize matroska muxer.
 */
static void
gst_matroska_mux_finalize (GObject * object)
{
  GstMatroskaMux *mux = GST_MATROSKA_MUX (object);

  gst_object_unref (mux->collect);
  gst_object_unref (mux->ebml_write);
364 365
  if (mux->writing_app)
    g_free (mux->writing_app);
366 367 368 369 370 371 372 373 374 375 376 377

  G_OBJECT_CLASS (parent_class)->finalize (object);
}


/**
 * gst_matroska_mux_create_uid:
 *
 * Generate new unused track UID.
 *
 * Returns: New track UID.
 */
378
static guint64
379
gst_matroska_mux_create_uid (void)
380
{
381
  guint64 uid = 0;
382

383 384 385 386
  G_LOCK (used_uids);

  if (!used_uids)
    used_uids = g_array_sized_new (FALSE, FALSE, sizeof (guint64), 10);
387 388 389 390

  while (!uid) {
    guint i;

391
    uid = (((guint64) g_random_int ()) << 32) | g_random_int ();
392
    for (i = 0; i < used_uids->len; i++) {
393
      if (g_array_index (used_uids, guint64, i) == uid) {
394 395 396 397 398 399
        uid = 0;
        break;
      }
    }
    g_array_append_val (used_uids, uid);
  }
400 401

  G_UNLOCK (used_uids);
402 403 404
  return uid;
}

405

406
/**
407
 * gst_matroska_pad_reset:
408 409
 * @collect_pad: the #GstMatroskaPad
 *
410
 * Reset and/or release resources of a matroska collect pad.
411 412
 */
static void
413
gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full)
414
{
415 416
  gchar *name = NULL;
  GstMatroskaTrackType type = 0;
417

418
  /* free track information */
419
  if (collect_pad->track != NULL) {
420 421 422 423 424 425 426 427 428 429 430 431 432
    /* retrieve for optional later use */
    name = collect_pad->track->name;
    type = collect_pad->track->type;
    /* extra for video */
    if (type == GST_MATROSKA_TRACK_TYPE_VIDEO) {
      GstMatroskaTrackVideoContext *ctx =
          (GstMatroskaTrackVideoContext *) collect_pad->track;

      if (ctx->dirac_unit) {
        gst_buffer_unref (ctx->dirac_unit);
        ctx->dirac_unit = NULL;
      }
    }
433 434
    g_free (collect_pad->track->codec_id);
    g_free (collect_pad->track->codec_name);
435 436
    if (full)
      g_free (collect_pad->track->name);
437 438 439 440 441 442 443 444 445 446 447
    g_free (collect_pad->track->language);
    g_free (collect_pad->track->codec_priv);
    g_free (collect_pad->track);
    collect_pad->track = NULL;
  }

  /* free cached buffer */
  if (collect_pad->buffer != NULL) {
    gst_buffer_unref (collect_pad->buffer);
    collect_pad->buffer = NULL;
  }
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492

  if (!full && type != 0) {
    GstMatroskaTrackContext *context;

    /* create a fresh context */
    switch (type) {
      case GST_MATROSKA_TRACK_TYPE_VIDEO:
        context = (GstMatroskaTrackContext *)
            g_new0 (GstMatroskaTrackVideoContext, 1);
        break;
      case GST_MATROSKA_TRACK_TYPE_AUDIO:
        context = (GstMatroskaTrackContext *)
            g_new0 (GstMatroskaTrackAudioContext, 1);
        break;
      case GST_MATROSKA_TRACK_TYPE_SUBTITLE:
        context = (GstMatroskaTrackContext *)
            g_new0 (GstMatroskaTrackSubtitleContext, 1);
        break;
      default:
        g_assert_not_reached ();
        break;
    }

    context->type = type;
    context->name = name;
    /* TODO: check default values for the context */
    context->flags = GST_MATROSKA_TRACK_ENABLED | GST_MATROSKA_TRACK_DEFAULT;
    collect_pad->track = context;
    collect_pad->buffer = NULL;
    collect_pad->duration = 0;
    collect_pad->start_ts = GST_CLOCK_TIME_NONE;
    collect_pad->end_ts = GST_CLOCK_TIME_NONE;
  }
}

/**
 * gst_matroska_pad_free:
 * @collect_pad: the #GstMatroskaPad
 *
 * Release resources of a matroska collect pad.
 */
static void
gst_matroska_pad_free (GstMatroskaPad * collect_pad)
{
  gst_matroska_pad_reset (collect_pad, TRUE);
493 494
}

495 496 497 498 499 500 501

/**
 * gst_matroska_mux_reset:
 * @element: #GstMatroskaMux that should be reseted.
 *
 * Reset matroska muxer back to initial state.
 */
502
static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
503
gst_matroska_mux_reset (GstElement * element)
504 505
{
  GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
506 507 508 509
  GSList *walk;

  /* reset EBML write */
  gst_ebml_write_reset (mux->ebml_write);
510 511 512 513 514

  /* reset input */
  mux->state = GST_MATROSKA_MUX_STATE_START;

  /* clean up existing streams */
515 516

  for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
517 518 519 520
    GstMatroskaPad *collect_pad;

    collect_pad = (GstMatroskaPad *) walk->data;

521 522
    /* reset collect pad to pristine state */
    gst_matroska_pad_reset (collect_pad, FALSE);
523
  }
524 525 526 527 528 529 530

  /* reset indexes */
  mux->num_indexes = 0;
  g_free (mux->index);
  mux->index = NULL;

  /* reset timers */
531
  mux->time_scale = GST_MSECOND;
532
  mux->duration = 0;
533

534 535 536 537
  /* reset cluster */
  mux->cluster = 0;
  mux->cluster_time = 0;
  mux->cluster_pos = 0;
538

539
  /* reset tags */
540
  gst_tag_setter_reset_tags (GST_TAG_SETTER (mux));
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 568 569
/**
 * gst_matroska_mux_handle_src_event:
 * @pad: Pad which received the event.
 * @event: Received event.
 *
 * handle events - copied from oggmux without understanding 
 *
 * Returns: #TRUE on success.
 */
static gboolean
gst_matroska_mux_handle_src_event (GstPad * pad, GstEvent * event)
{
  GstEventType type;

  type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;

  switch (type) {
    case GST_EVENT_SEEK:
      /* disable seeking for now */
      return FALSE;
    default:
      break;
  }

  return gst_pad_event_default (pad, event);
}

570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
/**
 * gst_matroska_mux_handle_sink_event:
 * @pad: Pad which received the event.
 * @event: Received event.
 *
 * handle events - informational ones like tags
 *
 * Returns: #TRUE on success.
 */
static gboolean
gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event)
{
  GstMatroskaTrackContext *context;
  GstMatroskaPad *collect_pad;
  GstMatroskaMux *mux;
  GstTagList *list;
586
  gboolean ret = TRUE;
587 588 589

  mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad));

590
  /* FIXME: aren't we either leaking events here or doing a wrong unref? */
591
  switch (GST_EVENT_TYPE (event)) {
592 593 594
    case GST_EVENT_TAG:{
      gchar *lang = NULL;

595
      GST_DEBUG_OBJECT (mux, "received tag event");
596
      gst_event_parse_tag (event, &list);
597

598 599 600 601
      collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
      g_assert (collect_pad);
      context = collect_pad->track;
      g_assert (context);
602 603 604 605 606 607 608 609 610 611 612 613 614 615

      /* Matroska wants ISO 639-2B code, taglist most likely contains 639-1 */
      if (gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &lang)) {
        const gchar *lang_code;

        lang_code = gst_tag_get_language_code_iso_639_2B (lang);
        if (lang_code) {
          GST_INFO_OBJECT (pad, "Setting language to '%s'", lang_code);
          context->language = g_strdup (lang_code);
        } else {
          GST_WARNING_OBJECT (pad, "Did not get language code for '%s'", lang);
        }
        g_free (lang);
      }
616

617 618
      gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), list,
          gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (mux)));
619
      break;
620
    }
621 622 623 624
    case GST_EVENT_NEWSEGMENT:
      /* We don't support NEWSEGMENT events */
      ret = FALSE;
      gst_event_unref (event);
625 626 627 628 629 630
      break;
    default:
      break;
  }

  /* now GstCollectPads can take care of the rest, e.g. EOS */
631 632
  if (ret)
    ret = mux->collect_event (pad, event);
633 634 635 636 637
  gst_object_unref (mux);

  return ret;
}

638 639 640 641 642 643 644 645 646 647 648 649

/**
 * gst_matroska_mux_video_pad_setcaps:
 * @pad: Pad which got the caps.
 * @caps: New caps.
 *
 * Setcaps function for video sink pad.
 *
 * Returns: #TRUE on success.
 */
static gboolean
gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
650 651 652
{
  GstMatroskaTrackContext *context = NULL;
  GstMatroskaTrackVideoContext *videocontext;
653
  GstMatroskaMux *mux;
654
  GstMatroskaPad *collect_pad;
655
  GstStructure *structure;
656
  const gchar *mimetype;
657 658
  const GValue *value = NULL;
  const GstBuffer *codec_buf = NULL;
659
  gint width, height, pixel_width, pixel_height;
660
  gint fps_d, fps_n;
661

662 663
  mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));

664
  /* find context */
665 666 667 668
  collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
  g_assert (collect_pad);
  context = collect_pad->track;
  g_assert (context);
669 670 671 672
  g_assert (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO);
  videocontext = (GstMatroskaTrackVideoContext *) context;

  /* gst -> matroska ID'ing */
David Schleef's avatar
David Schleef committed
673 674 675 676
  structure = gst_caps_get_structure (caps, 0);

  mimetype = gst_structure_get_name (structure);

677 678 679 680 681
  if (!strcmp (mimetype, "video/x-theora")) {
    /* we'll extract the details later from the theora identification header */
    goto skip_details;
  }

David Schleef's avatar
David Schleef committed
682
  /* get general properties */
683 684 685 686 687
  /* spec says it is mandatory */
  if (!gst_structure_get_int (structure, "width", &width) ||
      !gst_structure_get_int (structure, "height", &height))
    goto refuse_caps;

David Schleef's avatar
David Schleef committed
688 689
  videocontext->pixel_width = width;
  videocontext->pixel_height = height;
690 691
  if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d)
      && fps_n > 0) {
692 693 694 695 696 697 698
    context->default_duration =
        gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n);
    GST_LOG_OBJECT (pad, "default duration = %" GST_TIME_FORMAT,
        GST_TIME_ARGS (context->default_duration));
  } else {
    context->default_duration = 0;
  }
699 700
  if (gst_structure_get_fraction (structure, "pixel-aspect-ratio",
          &pixel_width, &pixel_height)) {
David Schleef's avatar
David Schleef committed
701 702 703 704 705 706
    if (pixel_width > pixel_height) {
      videocontext->display_width = width * pixel_width / pixel_height;
      videocontext->display_height = height;
    } else if (pixel_width < pixel_height) {
      videocontext->display_width = width;
      videocontext->display_height = height * pixel_height / pixel_width;
707 708 709 710
    } else {
      videocontext->display_width = 0;
      videocontext->display_height = 0;
    }
David Schleef's avatar
David Schleef committed
711 712 713 714
  } else {
    videocontext->display_width = 0;
    videocontext->display_height = 0;
  }
715

716 717
skip_details:

David Schleef's avatar
David Schleef committed
718 719 720
  videocontext->asr_mode = GST_MATROSKA_ASPECT_RATIO_MODE_FREE;
  videocontext->fourcc = 0;

721 722 723 724 725
  /* TODO: - check if we handle all codecs by the spec, i.e. codec private
   *         data and other settings
   *       - add new formats
   */

726 727 728 729 730
  /* extract codec_data, may turn out needed */
  value = gst_structure_get_value (structure, "codec_data");
  if (value)
    codec_buf = gst_value_get_buffer (value);

David Schleef's avatar
David Schleef committed
731 732 733 734
  /* find type */
  if (!strcmp (mimetype, "video/x-raw-yuv")) {
    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED);
    gst_structure_get_fourcc (structure, "format", &videocontext->fourcc);
735
  } else if (!strcmp (mimetype, "image/jpeg")) {
David Schleef's avatar
David Schleef committed
736
    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
737 738 739 740
  } else if (!strcmp (mimetype, "video/x-xvid") /* MS/VfW compatibility cases */
      ||!strcmp (mimetype, "video/x-huffyuv")
      || !strcmp (mimetype, "video/x-divx")
      || !strcmp (mimetype, "video/x-dv")
741
      || !strcmp (mimetype, "video/x-h263")
742 743
      || !strcmp (mimetype, "video/x-msmpeg")
      || !strcmp (mimetype, "video/x-wmv")) {
744
    BITMAPINFOHEADER *bih;
745
    gint size = sizeof (BITMAPINFOHEADER);
746
    guint32 fourcc = 0;
David Schleef's avatar
David Schleef committed
747

748
    if (!strcmp (mimetype, "video/x-xvid"))
749
      fourcc = GST_MAKE_FOURCC ('X', 'V', 'I', 'D');
750
    else if (!strcmp (mimetype, "video/x-huffyuv"))
751
      fourcc = GST_MAKE_FOURCC ('H', 'F', 'Y', 'U');
752
    else if (!strcmp (mimetype, "video/x-dv"))
753
      fourcc = GST_MAKE_FOURCC ('D', 'V', 'S', 'D');
754
    else if (!strcmp (mimetype, "video/x-h263"))
755
      fourcc = GST_MAKE_FOURCC ('H', '2', '6', '3');
756 757 758 759 760 761
    else if (!strcmp (mimetype, "video/x-divx")) {
      gint divxversion;

      gst_structure_get_int (structure, "divxversion", &divxversion);
      switch (divxversion) {
        case 3:
762
          fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', '3');
763 764
          break;
        case 4:
765
          fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', 'X');
766 767
          break;
        case 5:
768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783
          fourcc = GST_MAKE_FOURCC ('D', 'X', '5', '0');
          break;
      }
    } else if (!strcmp (mimetype, "video/x-msmpeg")) {
      gint msmpegversion;

      gst_structure_get_int (structure, "msmpegversion", &msmpegversion);
      switch (msmpegversion) {
        case 41:
          fourcc = GST_MAKE_FOURCC ('M', 'P', 'G', '4');
          break;
        case 42:
          fourcc = GST_MAKE_FOURCC ('M', 'P', '4', '2');
          break;
        case 43:
          goto msmpeg43;
784 785
          break;
      }
786 787 788 789 790 791 792 793 794 795 796 797 798 799
    } else if (!strcmp (mimetype, "video/x-wmv")) {
      gint wmvversion;
      guint32 format;
      if (gst_structure_get_fourcc (structure, "format", &format)) {
        fourcc = format;
      } else if (gst_structure_get_int (structure, "wmvversion", &wmvversion)) {
        if (wmvversion == 2) {
          fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '2');
        } else if (wmvversion == 1) {
          fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '1');
        } else if (wmvversion == 3) {
          fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '3');
        }
      }
David Schleef's avatar
David Schleef committed
800
    }
801

802
    if (!fourcc)
803
      goto refuse_caps;
804

805 806 807 808 809 810 811 812 813 814 815 816 817
    bih = g_new0 (BITMAPINFOHEADER, 1);
    GST_WRITE_UINT32_LE (&bih->bi_size, size);
    GST_WRITE_UINT32_LE (&bih->bi_width, videocontext->pixel_width);
    GST_WRITE_UINT32_LE (&bih->bi_height, videocontext->pixel_height);
    GST_WRITE_UINT32_LE (&bih->bi_compression, fourcc);
    GST_WRITE_UINT16_LE (&bih->bi_planes, (guint16) 1);
    GST_WRITE_UINT16_LE (&bih->bi_bit_count, (guint16) 24);
    GST_WRITE_UINT32_LE (&bih->bi_size_image, videocontext->pixel_width *
        videocontext->pixel_height * 3);

    /* process codec private/initialization data, if any */
    if (codec_buf) {
      size += GST_BUFFER_SIZE (codec_buf);
818 819 820
      bih = g_realloc (bih, size);
      GST_WRITE_UINT32_LE (&bih->bi_size, size);
      memcpy ((guint8 *) bih + sizeof (BITMAPINFOHEADER),
821
          GST_BUFFER_DATA (codec_buf), GST_BUFFER_SIZE (codec_buf));
822
    }
823 824 825

    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
    context->codec_priv = (gpointer) bih;
826
    context->codec_priv_size = size;
827 828 829 830 831 832 833 834 835 836
  } else if (!strcmp (mimetype, "video/x-h264")) {
    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC);

    if (context->codec_priv != NULL) {
      g_free (context->codec_priv);
      context->codec_priv = NULL;
      context->codec_priv_size = 0;
    }

    /* Create avcC header */
837 838 839 840 841
    if (codec_buf != NULL) {
      context->codec_priv_size = GST_BUFFER_SIZE (codec_buf);
      context->codec_priv = g_malloc0 (context->codec_priv_size);
      memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
          context->codec_priv_size);
842
    }
843 844 845 846 847 848 849 850 851 852 853 854 855 856 857
  } else if (!strcmp (mimetype, "video/x-theora")) {
    const GValue *streamheader;

    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_THEORA);

    if (context->codec_priv != NULL) {
      g_free (context->codec_priv);
      context->codec_priv = NULL;
      context->codec_priv_size = 0;
    }

    streamheader = gst_structure_get_value (structure, "streamheader");
    if (!theora_streamheader_to_codecdata (streamheader, context)) {
      GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
          ("theora stream headers missing or malformed"));
858
      goto refuse_caps;
859
    }
860 861
  } else if (!strcmp (mimetype, "video/x-dirac")) {
    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_DIRAC);
David Schleef's avatar
David Schleef committed
862 863 864 865 866 867
  } else if (!strcmp (mimetype, "video/mpeg")) {
    gint mpegversion;

    gst_structure_get_int (structure, "mpegversion", &mpegversion);
    switch (mpegversion) {
      case 1:
868 869
        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG1);
        break;
David Schleef's avatar
David Schleef committed
870
      case 2:
871 872
        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG2);
        break;
873
      case 4:
874 875
        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
        break;
876
      default:
877
        goto refuse_caps;
David Schleef's avatar
David Schleef committed
878
    }
879

880 881 882 883 884 885 886
    /* global headers may be in codec data */
    if (codec_buf != NULL) {
      context->codec_priv_size = GST_BUFFER_SIZE (codec_buf);
      context->codec_priv = g_malloc0 (context->codec_priv_size);
      memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
          context->codec_priv_size);
    }
David Schleef's avatar
David Schleef committed
887
  } else if (!strcmp (mimetype, "video/x-msmpeg")) {
888 889
  msmpeg43:
    /* can only make it here if preceding case verified it was version 3 */
David Schleef's avatar
David Schleef committed
890
    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3);
891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909
  } else if (!strcmp (mimetype, "video/x-pn-realvideo")) {
    gint rmversion;
    const GValue *mdpr_data;

    gst_structure_get_int (structure, "rmversion", &rmversion);
    switch (rmversion) {
      case 1:
        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1);
        break;
      case 2:
        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2);
        break;
      case 3:
        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3);
        break;
      case 4:
        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4);
        break;
      default:
910
        goto refuse_caps;
911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927
    }

    mdpr_data = gst_structure_get_value (structure, "mdpr_data");
    if (mdpr_data != NULL) {
      guint8 *priv_data = NULL;
      guint priv_data_size = 0;

      GstBuffer *codec_data_buf = g_value_peek_pointer (mdpr_data);

      priv_data_size = GST_BUFFER_SIZE (codec_data_buf);
      priv_data = g_malloc0 (priv_data_size);

      memcpy (priv_data, GST_BUFFER_DATA (codec_data_buf), priv_data_size);

      context->codec_priv = priv_data;
      context->codec_priv_size = priv_data_size;
    }
928 929
  }

930 931 932 933 934 935 936 937 938
  return TRUE;

  /* ERRORS */
refuse_caps:
  {
    GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT,
        GST_PAD_NAME (pad), caps);
    return FALSE;
  }
939 940
}

941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
/* N > 0 to expect a particular number of headers, negative if the
   number of headers is variable */
static gboolean
xiphN_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context, GstBuffer ** p_buf0, int N)
{
  GstBuffer **buf = NULL;
  GArray *bufarr;
  guint8 *priv_data;
  guint bufi, i, offset, priv_data_size;

  if (streamheader == NULL)
    goto no_stream_headers;

  if (G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY)
    goto wrong_type;

  bufarr = g_value_peek_pointer (streamheader);
  if (bufarr->len <= 0 || bufarr->len > 255)    /* at least one header, and count stored in a byte */
    goto wrong_count;
  if (N > 0 && bufarr->len != N)
    goto wrong_count;

  context->xiph_headers_to_skip = bufarr->len;

  buf = (GstBuffer **) g_malloc0 (sizeof (GstBuffer *) * bufarr->len);
  for (i = 0; i < bufarr->len; i++) {
    GValue *bufval = &g_array_index (bufarr, GValue, i);

    if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
      g_free (buf);
      goto wrong_content_type;
    }

    buf[i] = g_value_peek_pointer (bufval);
  }

  priv_data_size = 1;
  if (bufarr->len > 0) {
    for (i = 0; i < bufarr->len - 1; i++) {
      priv_data_size += GST_BUFFER_SIZE (buf[i]) / 0xff + 1;
    }
  }

  for (i = 0; i < bufarr->len; ++i) {
    priv_data_size += GST_BUFFER_SIZE (buf[i]);
  }

  priv_data = g_malloc0 (priv_data_size);

  priv_data[0] = bufarr->len - 1;
  offset = 1;

  if (bufarr->len > 0) {
    for (bufi = 0; bufi < bufarr->len - 1; bufi++) {
      for (i = 0; i < GST_BUFFER_SIZE (buf[bufi]) / 0xff; ++i) {
        priv_data[offset++] = 0xff;
      }
      priv_data[offset++] = GST_BUFFER_SIZE (buf[bufi]) % 0xff;
    }
  }

  for (i = 0; i < bufarr->len; ++i) {
    memcpy (priv_data + offset, GST_BUFFER_DATA (buf[i]),
        GST_BUFFER_SIZE (buf[i]));
    offset += GST_BUFFER_SIZE (buf[i]);
  }

  context->codec_priv = priv_data;
  context->codec_priv_size = priv_data_size;

  if (p_buf0)
    *p_buf0 = gst_buffer_ref (buf[0]);

  g_free (buf);

  return TRUE;

/* ERRORS */
no_stream_headers:
  {
    GST_WARNING ("required streamheaders missing in sink caps!");
    return FALSE;
  }
wrong_type:
  {
    GST_WARNING ("streamheaders are not a GST_TYPE_ARRAY, but a %s",
        G_VALUE_TYPE_NAME (streamheader));
    return FALSE;
  }
wrong_count:
  {
1033
    GST_WARNING ("got %u streamheaders, not %d as expected", bufarr->len, N);
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
    return FALSE;
  }
wrong_content_type:
  {
    GST_WARNING ("streamheaders array does not contain GstBuffers");
    return FALSE;
  }
}

static gboolean
vorbis_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context)
{
  GstBuffer *buf0 = NULL;

1049
  if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3))
1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
    return FALSE;

  if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 4) {
    GST_WARNING ("First vorbis header too small, ignoring");
  } else {
    if (memcmp (GST_BUFFER_DATA (buf0) + 1, "vorbis", 6) == 0) {
      GstMatroskaTrackAudioContext *audiocontext;
      guint8 *hdr;

      hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 4;
      audiocontext = (GstMatroskaTrackAudioContext *) context;
      audiocontext->channels = GST_READ_UINT8 (hdr);
      audiocontext->samplerate = GST_READ_UINT32_LE (hdr + 1);
    }