matroska-mux.c 89.4 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
  ARG_DOCTYPE_VERSION,
Xavier Queralt's avatar
Xavier Queralt committed
65
  ARG_MIN_INDEX_INTERVAL,
66
  ARG_STREAMABLE
67
68
};

69
#define  DEFAULT_DOCTYPE_VERSION         2
70
#define  DEFAULT_WRITING_APP             "GStreamer Matroska muxer"
71
#define  DEFAULT_MIN_INDEX_INTERVAL      0
72
#define  DEFAULT_STREAMABLE              FALSE
73

Stefan Kost's avatar
Stefan Kost committed
74
75
76
/* WAVEFORMATEX is gst_riff_strf_auds + an extra guint16 extension size */
#define WAVEFORMATEX_SIZE  (2 + sizeof (gst_riff_strf_auds))

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
77
78
79
80
81
static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-matroska")
    );
82

David Schleef's avatar
David Schleef committed
83
84
85
#define COMMON_VIDEO_CAPS \
  "width = (int) [ 16, 4096 ], " \
  "height = (int) [ 16, 4096 ], " \
86
  "framerate = (fraction) [ 0, MAX ]"
87

88
89
90
91
#define COMMON_VIDEO_CAPS_NO_FRAMERATE \
  "width = (int) [ 16, 4096 ], " \
  "height = (int) [ 16, 4096 ] "

92
93
94
95
/* FIXME: 
 * * require codec data, etc as needed
 */

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

#define COMMON_AUDIO_CAPS \
135
136
  "channels = (int) [ 1, MAX ], " \
  "rate = (int) [ 1, MAX ]"
137
138

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

static GstStaticPadTemplate subtitlesink_templ =
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
199
200
201
202
GST_STATIC_PAD_TEMPLATE ("subtitle_%d",
    GST_PAD_SINK,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS_ANY);
203

204
static GArray *used_uids;
205
G_LOCK_DEFINE_STATIC (used_uids);
206

207
208
209
210
static void gst_matroska_mux_add_interfaces (GType type);

GST_BOILERPLATE_FULL (GstMatroskaMux, gst_matroska_mux, GstElement,
    GST_TYPE_ELEMENT, gst_matroska_mux_add_interfaces);
211
212
213

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

215
216
217
/* Pads collected callback */
static GstFlowReturn
gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data);
218
219

/* pad functions */
220
221
static gboolean gst_matroska_mux_handle_src_event (GstPad * pad,
    GstEvent * event);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
222
223
static GstPad *gst_matroska_mux_request_new_pad (GstElement * element,
    GstPadTemplate * templ, const gchar * name);
224
static void gst_matroska_mux_release_pad (GstElement * element, GstPad * pad);
225
226

/* gst internal change state handler */
227
228
static GstStateChangeReturn
gst_matroska_mux_change_state (GstElement * element, GstStateChange transition);
229
230

/* gobject bla bla */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
231
232
233
234
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);
235
236

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

239
/* uid generation */
240
static guint64 gst_matroska_mux_create_uid ();
241

242
243
244
245
static gboolean theora_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context);
static gboolean vorbis_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context);
246
247
static gboolean speex_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context);
248
249
static gboolean kate_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context);
250
251
static gboolean flac_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context);
252

253
254
255
256
257
258
259
260
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);
}

261
static void
262
gst_matroska_mux_base_init (gpointer g_class)
263
{
264
265
266
267
268
269
270
}

static void
gst_matroska_mux_class_init (GstMatroskaMuxClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;
271

272
273
274
275
  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;

  gst_element_class_add_pad_template (gstelement_class,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
276
      gst_static_pad_template_get (&videosink_templ));
277
  gst_element_class_add_pad_template (gstelement_class,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
278
      gst_static_pad_template_get (&audiosink_templ));
279
  gst_element_class_add_pad_template (gstelement_class,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
280
      gst_static_pad_template_get (&subtitlesink_templ));
281
  gst_element_class_add_pad_template (gstelement_class,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
282
      gst_static_pad_template_get (&src_templ));
283
  gst_element_class_set_details_simple (gstelement_class, "Matroska muxer",
284
285
      "Codec/Muxer",
      "Muxes video/audio/subtitle streams into a matroska stream",
286
      "GStreamer maintainers <gstreamer-devel@lists.sourceforge.net>");
287
288
289

  GST_DEBUG_CATEGORY_INIT (matroskamux_debug, "matroskamux", 0,
      "Matroska muxer");
290

291
  gobject_class->finalize = gst_matroska_mux_finalize;
292
293
294
295

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

296
297
298
299
  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));
300
301
  g_object_class_install_property (gobject_class, ARG_DOCTYPE_VERSION,
      g_param_spec_int ("version", "DocType version",
302
          "This parameter determines what Matroska features can be used.",
303
          1, 2, DEFAULT_DOCTYPE_VERSION, G_PARAM_READWRITE));
304
305
306
307
  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));
308
309
310
311
312
  g_object_class_install_property (gobject_class, ARG_STREAMABLE,
      g_param_spec_boolean ("streamable", "Determines whether output should "
          "be 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_STATIC_STRINGS));
313

314
315
316
317
318
319
  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);
320
321
}

322
323
324
325
326
327
328
329

/**
 * 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
330
static void
331
gst_matroska_mux_init (GstMatroskaMux * mux, GstMatroskaMuxClass * g_class)
332
{
333
334
335
336
337
338
  GstPadTemplate *templ;

  templ =
      gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src");
  mux->srcpad = gst_pad_new_from_template (templ, "src");

339
  gst_pad_set_event_function (mux->srcpad, gst_matroska_mux_handle_src_event);
340
341
  gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad);

342
343
  mux->collect = gst_collect_pads_new ();
  gst_collect_pads_set_function (mux->collect,
344
345
      (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_matroska_mux_collected),
      mux);
346

347
  mux->ebml_write = gst_ebml_write_new (mux->srcpad);
348
  mux->doctype = GST_MATROSKA_DOCTYPE_MATROSKA;
349

350
  /* property defaults */
351
  mux->doctype_version = DEFAULT_DOCTYPE_VERSION;
352
  mux->writing_app = g_strdup (DEFAULT_WRITING_APP);
353
  mux->min_index_interval = DEFAULT_MIN_INDEX_INTERVAL;
354
  mux->streamable = DEFAULT_STREAMABLE;
355

356
  /* initialize internal variables */
357
  mux->index = NULL;
358
359
360
361
  mux->num_streams = 0;
  mux->num_a_streams = 0;
  mux->num_t_streams = 0;
  mux->num_v_streams = 0;
362

363
  /* initialize remaining variables */
364
365
366
  gst_matroska_mux_reset (GST_ELEMENT (mux));
}

367
368
369
370
371
372
373
374
375
376
377
378
379
380

/**
 * 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);
381
382
  if (mux->writing_app)
    g_free (mux->writing_app);
383
384
385
386
387
388
389
390
391
392
393
394

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


/**
 * gst_matroska_mux_create_uid:
 *
 * Generate new unused track UID.
 *
 * Returns: New track UID.
 */
395
static guint64
396
gst_matroska_mux_create_uid (void)
397
{
398
  guint64 uid = 0;
399

400
401
402
403
  G_LOCK (used_uids);

  if (!used_uids)
    used_uids = g_array_sized_new (FALSE, FALSE, sizeof (guint64), 10);
404
405
406
407

  while (!uid) {
    guint i;

408
    uid = (((guint64) g_random_int ()) << 32) | g_random_int ();
409
    for (i = 0; i < used_uids->len; i++) {
410
      if (g_array_index (used_uids, guint64, i) == uid) {
411
412
413
414
415
416
        uid = 0;
        break;
      }
    }
    g_array_append_val (used_uids, uid);
  }
417
418

  G_UNLOCK (used_uids);
419
420
421
  return uid;
}

422

423
/**
424
 * gst_matroska_pad_reset:
425
426
 * @collect_pad: the #GstMatroskaPad
 *
427
 * Reset and/or release resources of a matroska collect pad.
428
429
 */
static void
430
gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full)
431
{
432
433
  gchar *name = NULL;
  GstMatroskaTrackType type = 0;
434

435
  /* free track information */
436
  if (collect_pad->track != NULL) {
437
438
439
440
441
442
443
444
445
446
447
448
449
    /* 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;
      }
    }
450
451
    g_free (collect_pad->track->codec_id);
    g_free (collect_pad->track->codec_name);
452
453
    if (full)
      g_free (collect_pad->track->name);
454
455
456
457
458
459
460
461
462
463
464
    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;
  }
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
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509

  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);
510
511
}

512
513
514
515
516
517
518

/**
 * gst_matroska_mux_reset:
 * @element: #GstMatroskaMux that should be reseted.
 *
 * Reset matroska muxer back to initial state.
 */
519
static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
520
gst_matroska_mux_reset (GstElement * element)
521
522
{
  GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
523
524
525
526
  GSList *walk;

  /* reset EBML write */
  gst_ebml_write_reset (mux->ebml_write);
527
528
529
530
531

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

  /* clean up existing streams */
532
533

  for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
534
535
536
537
    GstMatroskaPad *collect_pad;

    collect_pad = (GstMatroskaPad *) walk->data;

538
539
    /* reset collect pad to pristine state */
    gst_matroska_pad_reset (collect_pad, FALSE);
540
  }
541
542
543
544
545
546
547

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

  /* reset timers */
548
  mux->time_scale = GST_MSECOND;
549
  mux->max_cluster_duration = G_MAXINT16 * mux->time_scale;
550
  mux->duration = 0;
551

552
553
554
555
  /* reset cluster */
  mux->cluster = 0;
  mux->cluster_time = 0;
  mux->cluster_pos = 0;
556
  mux->prev_cluster_size = 0;
557

558
  /* reset tags */
559
  gst_tag_setter_reset_tags (GST_TAG_SETTER (mux));
560
561
}

562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
/**
 * 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);
}

589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
/**
 * 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;
605
  gboolean ret = TRUE;
606
607
608

  mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad));

609
  /* FIXME: aren't we either leaking events here or doing a wrong unref? */
610
  switch (GST_EVENT_TYPE (event)) {
611
612
613
    case GST_EVENT_TAG:{
      gchar *lang = NULL;

614
      GST_DEBUG_OBJECT (mux, "received tag event");
615
      gst_event_parse_tag (event, &list);
616

617
618
619
620
      collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
      g_assert (collect_pad);
      context = collect_pad->track;
      g_assert (context);
621
622
623
624
625
626
627
628
629
630
631
632
633
634

      /* 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);
      }
635

636
637
      gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), list,
          gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (mux)));
638
      break;
639
    }
640
641
642
643
    case GST_EVENT_NEWSEGMENT:
      /* We don't support NEWSEGMENT events */
      ret = FALSE;
      gst_event_unref (event);
644
645
646
647
648
649
      break;
    default:
      break;
  }

  /* now GstCollectPads can take care of the rest, e.g. EOS */
650
651
  if (ret)
    ret = mux->collect_event (pad, event);
652
653
654
655
656
  gst_object_unref (mux);

  return ret;
}

657
658
659
660
661
662
663
664
665
666
667
668

/**
 * 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)
669
670
671
{
  GstMatroskaTrackContext *context = NULL;
  GstMatroskaTrackVideoContext *videocontext;
672
  GstMatroskaMux *mux;
673
  GstMatroskaPad *collect_pad;
674
  GstStructure *structure;
675
  const gchar *mimetype;
676
677
  const GValue *value = NULL;
  const GstBuffer *codec_buf = NULL;
678
  gint width, height, pixel_width, pixel_height;
679
  gint fps_d, fps_n;
680
  gboolean interlaced = FALSE;
681

682
683
  mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));

684
  /* find context */
685
686
687
688
  collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
  g_assert (collect_pad);
  context = collect_pad->track;
  g_assert (context);
689
690
691
692
  g_assert (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO);
  videocontext = (GstMatroskaTrackVideoContext *) context;

  /* gst -> matroska ID'ing */
David Schleef's avatar
David Schleef committed
693
694
695
696
  structure = gst_caps_get_structure (caps, 0);

  mimetype = gst_structure_get_name (structure);

697
698
699
700
  if (gst_structure_get_boolean (structure, "interlaced", &interlaced)
      && interlaced)
    context->flags |= GST_MATROSKA_VIDEOTRACK_INTERLACED;

701
702
703
704
705
  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
706
  /* get general properties */
707
708
709
710
711
  /* 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
712
713
  videocontext->pixel_width = width;
  videocontext->pixel_height = height;
714
715
  if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d)
      && fps_n > 0) {
716
717
718
719
720
721
722
    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;
  }
723
724
  if (gst_structure_get_fraction (structure, "pixel-aspect-ratio",
          &pixel_width, &pixel_height)) {
David Schleef's avatar
David Schleef committed
725
726
727
728
729
730
    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;
731
732
733
734
    } else {
      videocontext->display_width = 0;
      videocontext->display_height = 0;
    }
David Schleef's avatar
David Schleef committed
735
736
737
738
  } else {
    videocontext->display_width = 0;
    videocontext->display_height = 0;
  }
739

740
741
skip_details:

David Schleef's avatar
David Schleef committed
742
743
744
  videocontext->asr_mode = GST_MATROSKA_ASPECT_RATIO_MODE_FREE;
  videocontext->fourcc = 0;

745
746
747
748
749
  /* TODO: - check if we handle all codecs by the spec, i.e. codec private
   *         data and other settings
   *       - add new formats
   */

750
751
752
753
754
  /* 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
755
756
757
758
  /* 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);
759
  } else if (!strcmp (mimetype, "image/jpeg")) {
David Schleef's avatar
David Schleef committed
760
    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
761
762
763
764
  } 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")
765
      || !strcmp (mimetype, "video/x-h263")
766
767
      || !strcmp (mimetype, "video/x-msmpeg")
      || !strcmp (mimetype, "video/x-wmv")) {
Stefan Kost's avatar
Stefan Kost committed
768
769
    gst_riff_strf_vids *bih;
    gint size = sizeof (gst_riff_strf_vids);
770
    guint32 fourcc = 0;
David Schleef's avatar
David Schleef committed
771

772
    if (!strcmp (mimetype, "video/x-xvid"))
773
      fourcc = GST_MAKE_FOURCC ('X', 'V', 'I', 'D');
774
    else if (!strcmp (mimetype, "video/x-huffyuv"))
775
      fourcc = GST_MAKE_FOURCC ('H', 'F', 'Y', 'U');
776
    else if (!strcmp (mimetype, "video/x-dv"))
777
      fourcc = GST_MAKE_FOURCC ('D', 'V', 'S', 'D');
778
    else if (!strcmp (mimetype, "video/x-h263"))
779
      fourcc = GST_MAKE_FOURCC ('H', '2', '6', '3');
780
781
782
783
784
785
    else if (!strcmp (mimetype, "video/x-divx")) {
      gint divxversion;

      gst_structure_get_int (structure, "divxversion", &divxversion);
      switch (divxversion) {
        case 3:
786
          fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', '3');
787
788
          break;
        case 4:
789
          fourcc = GST_MAKE_FOURCC ('D', 'I', 'V', 'X');
790
791
          break;
        case 5:
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
          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;
808
809
          break;
      }
810
811
812
813
814
815
816
817
818
819
820
821
822
823
    } 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
824
    }
825

826
    if (!fourcc)
827
      goto refuse_caps;
828

Stefan Kost's avatar
Stefan Kost committed
829
830
831
832
833
834
835
836
    bih = g_new0 (gst_riff_strf_vids, 1);
    GST_WRITE_UINT32_LE (&bih->size, size);
    GST_WRITE_UINT32_LE (&bih->width, videocontext->pixel_width);
    GST_WRITE_UINT32_LE (&bih->height, videocontext->pixel_height);
    GST_WRITE_UINT32_LE (&bih->compression, fourcc);
    GST_WRITE_UINT16_LE (&bih->planes, (guint16) 1);
    GST_WRITE_UINT16_LE (&bih->bit_cnt, (guint16) 24);
    GST_WRITE_UINT32_LE (&bih->image_size, videocontext->pixel_width *
837
838
839
840
841
        videocontext->pixel_height * 3);

    /* process codec private/initialization data, if any */
    if (codec_buf) {
      size += GST_BUFFER_SIZE (codec_buf);
842
      bih = g_realloc (bih, size);
Stefan Kost's avatar
Stefan Kost committed
843
844
      GST_WRITE_UINT32_LE (&bih->size, size);
      memcpy ((guint8 *) bih + sizeof (gst_riff_strf_vids),
845
          GST_BUFFER_DATA (codec_buf), GST_BUFFER_SIZE (codec_buf));
846
    }
847
848
849

    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
    context->codec_priv = (gpointer) bih;
850
    context->codec_priv_size = size;
851
852
853
854
855
856
857
858
859
860
  } 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 */
861
862
863
864
865
    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);
866
    }
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
  } 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"));
882
      goto refuse_caps;
883
    }
884
885
  } else if (!strcmp (mimetype, "video/x-dirac")) {
    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_DIRAC);
886
887
  } else if (!strcmp (mimetype, "video/x-vp8")) {
    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VP8);
David Schleef's avatar
David Schleef committed
888
889
890
891
892
893
  } else if (!strcmp (mimetype, "video/mpeg")) {
    gint mpegversion;

    gst_structure_get_int (structure, "mpegversion", &mpegversion);
    switch (mpegversion) {
      case 1:
894
895
        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG1);
        break;
David Schleef's avatar
David Schleef committed
896
      case 2:
897
898
        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG2);
        break;
899
      case 4:
900
901
        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
        break;
902
      default:
903
        goto refuse_caps;
David Schleef's avatar
David Schleef committed
904
    }
905

906
907
908
909
910
911
912
    /* 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
913
  } else if (!strcmp (mimetype, "video/x-msmpeg")) {
914
915
  msmpeg43:
    /* can only make it here if preceding case verified it was version 3 */
David Schleef's avatar
David Schleef committed
916
    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3);
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
  } 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:
936
        goto refuse_caps;
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
    }

    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;
    }
954
955
  }

956
957
958
959
960
961
962
963
964
  return TRUE;

  /* ERRORS */
refuse_caps:
  {
    GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT,
        GST_PAD_NAME (pad), caps);
    return FALSE;
  }
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
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
/* 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:
  {
1059
    GST_WARNING ("got %u streamheaders, not %d as expected", bufarr->len, N);
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
    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;

1075
  if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3))
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
    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);
    }
  }

  if (buf0)
    gst_buffer_unref (buf0);

  return TRUE;
}

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

1104
  if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3))
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
    return FALSE;

  if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 26) {
    GST_WARNING ("First theora header too small, ignoring");
  } else if (memcmp (GST_BUFFER_DATA (buf0), "\200theora\003\002", 9) != 0) {
    GST_WARNING ("First header not a theora identification header, ignoring");
  } else {
    GstMatroskaTrackVideoContext *videocontext;
    guint fps_num, fps_denom, par_num, par_denom;
    guint8 *hdr;

    hdr = GST_BUFFER_DATA (buf0) + 1 + 6 + 3 + 2 + 2;

    videocontext = (GstMatroskaTrackVideoContext *) context;
    videocontext->pixel_width = GST_READ_UINT32_BE (hdr) >> 8;
    videocontext->pixel_height = GST_READ_UINT32_BE (hdr + 3) >> 8;
    hdr += 3 + 3 + 1 + 1;
    fps_num = GST_READ_UINT32_BE (hdr);
    fps_denom = GST_READ_UINT32_BE (hdr + 4);
    context->default_duration = gst_util_uint64_scale_int (GST_SECOND,
        fps_denom, fps_num);
    hdr += 4 + 4;
    par_num = GST_READ_UINT32_BE (hdr) >> 8;
    par_denom = GST_READ_UINT32_BE (hdr + 3) >> 8;
    if (par_num > 0 && par_num > 0) {
      if (par_num > par_denom) {
        videocontext->display_width =
            videocontext->pixel_width * par_num / par_denom;
        videocontext->display_height = videocontext->pixel_height;
      } else if (par_num < par_denom) {
        videocontext->display_width = videocontext->pixel_width;
        videocontext->display_height =
            videocontext->pixel_height * par_denom / par_num;
      } else {
        videocontext->display_width = 0;
        videocontext->display_height = 0;
      }
    } else {
      videocontext->display_width = 0;
      videocontext->display_height = 0;
    }
    hdr += 3 + 3;
  }

  if (buf0)
    gst_buffer_unref (buf0);

  return TRUE;
}
1154

1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
static gboolean
kate_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context)
{
  GstBuffer *buf0 = NULL;

  if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, -1))
    return FALSE;

  if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 64) {    /* Kate ID header is 64 bytes */
    GST_WARNING ("First kate header too small, ignoring");
  } else if (memcmp (GST_BUFFER_DATA (buf0), "\200kate\0\0\0", 8) != 0) {
    GST_WARNING ("First header not a kate identification header, ignoring");
  }

  if (buf0)
    gst_buffer_unref (buf0);

  return TRUE;
}

1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
static gboolean
flac_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context)
{
  GArray *bufarr;
  gint i;
  GValue *bufval;
  GstBuffer *buffer;

  if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) {
    GST_WARNING ("No or invalid streamheader field in the caps");
    return FALSE;
  }

  bufarr = g_value_peek_pointer (streamheader);
  if (bufarr->len < 2) {
    GST_WARNING ("Too few headers in streamheader field");
    return FALSE;
  }

1196
  context->xiph_headers_to_skip = bufarr->len + 1;
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234

  bufval = &g_array_index (bufarr, GValue, 0);
  if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
    GST_WARNING ("streamheaders array does not contain GstBuffers");
    return FALSE;
  }

  buffer = g_value_peek_pointer (bufval);

  /* Need at least OggFLAC mapping header, fLaC marker and STREAMINFO block */
  if (GST_BUFFER_SIZE (buffer) < 9 + 4 + 4 + 34
      || memcmp (GST_BUFFER_DATA (buffer) + 1, "FLAC", 4) != 0
      || memcmp (GST_BUFFER_DATA (buffer) + 9, "fLaC", 4) != 0) {
    GST_WARNING ("Invalid streamheader for FLAC");
    return FALSE;
  }

  context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer) - 9);
  context->codec_priv_size = GST_BUFFER_SIZE (buffer) - 9;
  memcpy (context->codec_priv, GST_BUFFER_DATA (buffer) + 9,
      GST_BUFFER_SIZE (buffer) - 9);

  for (i = 1; i < bufarr->len; i++) {
    bufval = &g_array_index (bufarr, GValue, i);

    if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
      g_free (context->codec_priv);
      context->codec_priv = NULL;
      context->codec_priv_size = 0;
      GST_WARNING ("streamheaders array does not contain GstBuffers");
      return FALSE;
    }

    buffer = g_value_peek_pointer (bufval);

    context->codec_priv =
        g_realloc (context->codec_priv,
        context->codec_priv_size + GST_BUFFER_SIZE (buffer));
Jan Schmidt's avatar
Jan Schmidt committed
1235
    memcpy ((guint8 *) context->codec_priv + context->codec_priv_size,
1236
1237
1238
1239
1240
1241
1242
1243
        GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
    context->codec_priv_size =
        context->codec_priv_size + GST_BUFFER_SIZE (buffer);
  }

  return TRUE;
}

1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
static gboolean
speex_streamheader_to_codecdata (const GValue * streamheader,
    GstMatroskaTrackContext * context)
{
  GArray *bufarr;
  GValue *bufval;
  GstBuffer *buffer;

  if (streamheader == NULL || G_VALUE_TYPE (streamheader) != GST_TYPE_ARRAY) {
    GST_WARNING ("No or invalid streamheader field in the caps");
    return FALSE;
  }

  bufarr = g_value_peek_pointer (streamheader);
  if (bufarr->len != 2) {
    GST_WARNING ("Too few headers in streamheader field");
    return FALSE;
  }

  context->xiph_headers_to_skip = bufarr->len + 1;

  bufval = &g_array_index (bufarr, GValue, 0);
  if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
    GST_WARNING ("streamheaders array does not contain GstBuffers");
    return FALSE;
  }

  buffer = g_value_peek_pointer (bufval);

  if (GST_BUFFER_SIZE (buffer) < 80
      || memcmp (GST_BUFFER_DATA (buffer), "Speex   ", 8) != 0) {
    GST_WARNING ("Invalid streamheader for Speex");
    return FALSE;
  }

  context->codec_priv = g_malloc (GST_BUFFER_SIZE (buffer));
  context->codec_priv_size = GST_BUFFER_SIZE (buffer);
  memcpy (context->codec_priv, GST_BUFFER_DATA (buffer),
      GST_BUFFER_SIZE (buffer));

  bufval = &g_array_index (bufarr, GValue, 1);

  if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
    g_free (context->codec_priv);
    context->codec_priv = NULL;
    context->codec_priv_size = 0;
    GST_WARNING ("streamheaders array does not contain GstBuffers");
    return FALSE;
  }

  buffer = g_value_peek_pointer (bufval);

  context->codec_priv =
      g_realloc (context->codec_priv,
      context->codec_priv_size + GST_BUFFER_SIZE (buffer));
  memcpy ((guint8 *) context->codec_priv + context->codec_priv_size,
      GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer));
  context->codec_priv_size =
      context->codec_priv_size + GST_BUFFER_SIZE (buffer);

  return TRUE;
}

1307
static const gchar *
1308
1309
aac_codec_data_to_codec_id (const GstBuffer * buf)
{
1310
  const gchar *result;
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
  gint profile;

  /* default to MAIN */
  profile = 1;

  if (GST_BUFFER_SIZE (buf) >= 2) {
    profile = GST_READ_UINT8 (GST_BUFFER_DATA (buf));
    profile >>= 3;
  }

  switch (profile) {
    case 1:
      result = "MAIN";
      break;
    case 2:
      result = "LC";
      break;
    case 3:
      result = "SSR";
      break;
    case 4:
      result = "LTP";
      break;
    default:
      GST_WARNING ("unknown AAC profile, defaulting to MAIN");
      result = "MAIN";
      break;
  }

  return result;
}

1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
/**
 * gst_matroska_mux_audio_pad_setcaps:
 * @pad: Pad which got the caps.
 * @caps: New caps.
 *
 * Setcaps function for audio sink pad.
 *
 * Returns: #TRUE on success.
 */
static gboolean
gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
1354
1355
1356
{
  GstMatroskaTrackContext *context = NULL;
  GstMatroskaTrackAudioContext *audiocontext;
1357
  GstMatroskaMux *mux;
1358
  GstMatroskaPad *collect_pad;
1359
  const gchar *mimetype;