qtmux.c 27.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/* GStreamer
 *
 * unit test for qtmux
 *
 * Copyright (C) <2008> Mark Nauwelaerts <mnauw@users.sf.net>
 *
 * 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
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_UNISTD_H
28
#include <unistd.h>
29
#endif
30

31 32
#include <glib/gstdio.h>

33
#include <gst/check/gstcheck.h>
34
#include <gst/pbutils/encoding-profile.h>
35 36 37 38 39 40 41 42 43 44 45

/* For ease of programming we use globals to keep refs for our floating
 * src and sink pads we create; otherwise we always have to do get_pad,
 * get_peer, and then remove references in every test function */
static GstPad *mysrcpad, *mysinkpad;

#define AUDIO_CAPS_STRING "audio/mpeg, " \
                        "mpegversion = (int) 1, " \
                        "layer = (int) 3, " \
                        "channels = (int) 2, " \
                        "rate = (int) 48000"
46 47 48 49 50 51 52 53 54 55 56

#define AUDIO_AAC_CAPS_STRING "audio/mpeg, " \
                            "mpegversion=(int)4, " \
                            "channels=(int)1, " \
                            "rate=(int)44100, " \
                            "stream-format=(string)raw, " \
                            "level=(string)2, " \
                            "base-profile=(string)lc, " \
                            "profile=(string)lc, " \
                            "codec_data=(buffer)1208"

57 58
#define VIDEO_CAPS_STRING "video/mpeg, " \
                           "mpegversion = (int) 4, " \
59
                           "systemstream = (boolean) false, " \
60 61 62 63
                           "width = (int) 384, " \
                           "height = (int) 288, " \
                           "framerate = (fraction) 25/1"

64 65 66 67 68 69 70 71 72 73 74 75 76
#define VIDEO_CAPS_H264_STRING "video/x-h264, " \
                               "width=(int)320, " \
                               "height=(int)240, " \
                               "framerate=(fraction)30/1, " \
                               "pixel-aspect-ratio=(fraction)1/1, " \
                               "codec_data=(buffer)01640014ffe1001867640014a" \
                                   "cd94141fb0110000003001773594000f14299600" \
                                   "1000568ebecb22c, " \
                               "stream-format=(string)avc, " \
                               "alignment=(string)au, " \
                               "level=(string)2, " \
                               "profile=(string)high"

77 78 79 80 81 82 83 84 85
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/quicktime"));
static GstStaticPadTemplate srcvideotemplate = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (VIDEO_CAPS_STRING));

86 87 88 89 90 91
static GstStaticPadTemplate srcvideoh264template =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (VIDEO_CAPS_H264_STRING));

92 93 94 95 96
static GstStaticPadTemplate srcaudiotemplate = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (AUDIO_CAPS_STRING));

97 98 99 100 101
static GstStaticPadTemplate srcaudioaactemplate =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (AUDIO_AAC_CAPS_STRING));
102 103

/* setup and teardown needs some special handling for muxer */
104
static GstPad *
105
setup_src_pad (GstElement * element,
106
    GstStaticPadTemplate * template, const gchar * sinkname)
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
{
  GstPad *srcpad, *sinkpad;

  GST_DEBUG_OBJECT (element, "setting up sending pad");
  /* sending pad */
  srcpad = gst_pad_new_from_static_template (template, "src");
  fail_if (srcpad == NULL, "Could not create a srcpad");
  ASSERT_OBJECT_REFCOUNT (srcpad, "srcpad", 1);

  if (!(sinkpad = gst_element_get_static_pad (element, sinkname)))
    sinkpad = gst_element_get_request_pad (element, sinkname);
  fail_if (sinkpad == NULL, "Could not get sink pad from %s",
      GST_ELEMENT_NAME (element));
  /* references are owned by: 1) us, 2) qtmux, 3) collect pads */
  ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 3);
  fail_unless (gst_pad_link (srcpad, sinkpad) == GST_PAD_LINK_OK,
      "Could not link source and %s sink pads", GST_ELEMENT_NAME (element));
  gst_object_unref (sinkpad);   /* because we got it higher up */

  /* references are owned by: 1) qtmux, 2) collect pads */
  ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 2);

  return srcpad;
}

132
static void
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
teardown_src_pad (GstPad * srcpad)
{
  GstPad *sinkpad;

  /* clean up floating src pad */
  sinkpad = gst_pad_get_peer (srcpad);
  fail_if (sinkpad == NULL);
  /* pad refs held by 1) qtmux 2) collectpads and 3) us (through _get_peer) */
  ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 3);

  gst_pad_unlink (srcpad, sinkpad);

  /* after unlinking, pad refs still held by
   * 1) qtmux and 2) collectpads and 3) us (through _get_peer) */
  ASSERT_OBJECT_REFCOUNT (sinkpad, "sinkpad", 3);
  gst_object_unref (sinkpad);
  /* one more ref is held by element itself */

  /* pad refs held by creator */
  ASSERT_OBJECT_REFCOUNT (srcpad, "srcpad", 1);
  gst_object_unref (srcpad);
}

156
static GstElement *
Benjamin Otte's avatar
Benjamin Otte committed
157
setup_qtmux (GstStaticPadTemplate * srctemplate, const gchar * sinkname)
158 159 160 161 162
{
  GstElement *qtmux;

  GST_DEBUG ("setup_qtmux");
  qtmux = gst_check_setup_element ("qtmux");
163
  mysrcpad = setup_src_pad (qtmux, srctemplate, sinkname);
164
  mysinkpad = gst_check_setup_sink_pad (qtmux, &sinktemplate);
165 166 167 168 169 170
  gst_pad_set_active (mysrcpad, TRUE);
  gst_pad_set_active (mysinkpad, TRUE);

  return qtmux;
}

171
static void
Benjamin Otte's avatar
Benjamin Otte committed
172
cleanup_qtmux (GstElement * qtmux, const gchar * sinkname)
173 174 175 176 177 178 179 180 181 182 183
{
  GST_DEBUG ("cleanup_qtmux");
  gst_element_set_state (qtmux, GST_STATE_NULL);

  gst_pad_set_active (mysrcpad, FALSE);
  gst_pad_set_active (mysinkpad, FALSE);
  teardown_src_pad (mysrcpad);
  gst_check_teardown_sink_pad (qtmux);
  gst_check_teardown_element (qtmux);
}

184
static void
185 186
check_qtmux_pad (GstStaticPadTemplate * srctemplate, const gchar * sinkname,
    guint32 dts_method)
187 188 189 190 191 192 193 194 195
{
  GstElement *qtmux;
  GstBuffer *inbuffer, *outbuffer;
  GstCaps *caps;
  int num_buffers;
  int i;
  guint8 data0[12] = "\000\000\000\024ftypqt  ";
  guint8 data1[8] = "\000\000\000\001mdat";
  guint8 data2[4] = "moov";
196
  GstSegment segment;
197 198

  qtmux = setup_qtmux (srctemplate, sinkname);
199
  g_object_set (qtmux, "dts-method", dts_method, NULL);
200 201 202 203
  fail_unless (gst_element_set_state (qtmux,
          GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
      "could not set to playing");

204 205 206 207 208 209
  gst_pad_push_event (mysrcpad, gst_event_new_stream_start ("test"));

  caps = gst_pad_get_pad_template_caps (mysrcpad);
  gst_pad_set_caps (mysrcpad, caps);
  gst_caps_unref (caps);

210 211 212 213
  /* ensure segment (format) properly setup */
  gst_segment_init (&segment, GST_FORMAT_TIME);
  fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)));

214
  inbuffer = gst_buffer_new_and_alloc (1);
215
  gst_buffer_memset (inbuffer, 0, 0, 1);
216 217 218 219 220 221 222 223 224 225 226 227
  GST_BUFFER_TIMESTAMP (inbuffer) = 0;
  GST_BUFFER_DURATION (inbuffer) = 40 * GST_MSECOND;
  ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
  fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK);

  /* send eos to have moov written */
  fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()) == TRUE);

  num_buffers = g_list_length (buffers);
  /* at least expect ftyp, mdat header, buffer chunk and moov */
  fail_unless (num_buffers >= 4);

228 229 230
  /* clean up first to clear any pending refs in sticky caps */
  cleanup_qtmux (qtmux, sinkname);

231 232 233 234 235 236 237 238 239
  for (i = 0; i < num_buffers; ++i) {
    outbuffer = GST_BUFFER (buffers->data);
    fail_if (outbuffer == NULL);
    buffers = g_list_remove (buffers, outbuffer);

    switch (i) {
      case 0:
      {
        /* ftyp header */
240 241 242 243
        fail_unless (gst_buffer_get_size (outbuffer) >= 20);
        fail_unless (gst_buffer_memcmp (outbuffer, 0, data0,
                sizeof (data0)) == 0);
        fail_unless (gst_buffer_memcmp (outbuffer, 16, data0 + 8, 4) == 0);
244 245 246
        break;
      }
      case 1:                  /* mdat header */
247 248
        fail_unless (gst_buffer_get_size (outbuffer) == 16);
        fail_unless (gst_buffer_memcmp (outbuffer, 0, data1, sizeof (data1))
249 250 251
            == 0);
        break;
      case 2:                  /* buffer we put in */
252
        fail_unless (gst_buffer_get_size (outbuffer) == 1);
253 254
        break;
      case 3:                  /* moov */
255 256
        fail_unless (gst_buffer_get_size (outbuffer) > 8);
        fail_unless (gst_buffer_memcmp (outbuffer, 4, data2,
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
                sizeof (data2)) == 0);
        break;
      default:
        break;
    }

    ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1);
    gst_buffer_unref (outbuffer);
    outbuffer = NULL;
  }

  g_list_free (buffers);
  buffers = NULL;
}

272 273
static void
check_qtmux_pad_fragmented (GstStaticPadTemplate * srctemplate,
274
    const gchar * sinkname, guint32 dts_method, gboolean streamable)
275 276 277 278 279 280 281 282 283 284 285
{
  GstElement *qtmux;
  GstBuffer *inbuffer, *outbuffer;
  GstCaps *caps;
  int num_buffers;
  int i;
  guint8 data0[12] = "\000\000\000\024ftypqt  ";
  guint8 data1[4] = "mdat";
  guint8 data2[4] = "moov";
  guint8 data3[4] = "moof";
  guint8 data4[4] = "mfra";
286
  GstSegment segment;
287 288

  qtmux = setup_qtmux (srctemplate, sinkname);
289
  g_object_set (qtmux, "dts-method", dts_method, NULL);
290 291 292 293 294 295
  g_object_set (qtmux, "fragment-duration", 2000, NULL);
  g_object_set (qtmux, "streamable", streamable, NULL);
  fail_unless (gst_element_set_state (qtmux,
          GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
      "could not set to playing");

296 297 298 299 300 301
  gst_pad_push_event (mysrcpad, gst_event_new_stream_start ("test"));

  caps = gst_pad_get_pad_template_caps (mysrcpad);
  gst_pad_set_caps (mysrcpad, caps);
  gst_caps_unref (caps);

302 303 304 305
  /* ensure segment (format) properly setup */
  gst_segment_init (&segment, GST_FORMAT_TIME);
  fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)));

306
  inbuffer = gst_buffer_new_and_alloc (1);
307
  gst_buffer_memset (inbuffer, 0, 0, 1);
308 309 310 311 312 313 314 315 316 317 318 319 320
  GST_BUFFER_TIMESTAMP (inbuffer) = 0;
  GST_BUFFER_DURATION (inbuffer) = 40 * GST_MSECOND;
  ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
  fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK);

  /* send eos to have all written */
  fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()) == TRUE);

  num_buffers = g_list_length (buffers);
  /* at least expect ftyp, moov, moof, mdat header, buffer chunk
   * and optionally mfra */
  fail_unless (num_buffers >= 5);

321 322 323
  /* clean up first to clear any pending refs in sticky caps */
  cleanup_qtmux (qtmux, sinkname);

324 325 326 327 328 329 330 331 332
  for (i = 0; i < num_buffers; ++i) {
    outbuffer = GST_BUFFER (buffers->data);
    fail_if (outbuffer == NULL);
    buffers = g_list_remove (buffers, outbuffer);

    switch (i) {
      case 0:
      {
        /* ftyp header */
333 334 335 336
        fail_unless (gst_buffer_get_size (outbuffer) >= 20);
        fail_unless (gst_buffer_memcmp (outbuffer, 0, data0,
                sizeof (data0)) == 0);
        fail_unless (gst_buffer_memcmp (outbuffer, 16, data0 + 8, 4) == 0);
337 338 339
        break;
      }
      case 1:                  /* moov */
340 341
        fail_unless (gst_buffer_get_size (outbuffer) > 8);
        fail_unless (gst_buffer_memcmp (outbuffer, 4, data2,
342 343 344
                sizeof (data2)) == 0);
        break;
      case 2:                  /* moof */
345 346
        fail_unless (gst_buffer_get_size (outbuffer) > 8);
        fail_unless (gst_buffer_memcmp (outbuffer, 4, data3,
347 348 349
                sizeof (data3)) == 0);
        break;
      case 3:                  /* mdat header */
350 351
        fail_unless (gst_buffer_get_size (outbuffer) == 8);
        fail_unless (gst_buffer_memcmp (outbuffer, 4, data1,
352 353 354
                sizeof (data1)) == 0);
        break;
      case 4:                  /* buffer we put in */
355
        fail_unless (gst_buffer_get_size (outbuffer) == 1);
356 357
        break;
      case 5:                  /* mfra */
358 359
        fail_unless (gst_buffer_get_size (outbuffer) > 8);
        fail_unless (gst_buffer_memcmp (outbuffer, 4, data4,
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
                sizeof (data4)) == 0);
        break;
      default:
        break;
    }

    ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1);
    gst_buffer_unref (outbuffer);
    outbuffer = NULL;
  }

  g_list_free (buffers);
  buffers = NULL;
}

375
/* dts-method dd */
376

377
GST_START_TEST (test_video_pad_dd)
378
{
379
  check_qtmux_pad (&srcvideotemplate, "video_%u", 0);
380 381 382 383
}

GST_END_TEST;

384
GST_START_TEST (test_audio_pad_dd)
385
{
386
  check_qtmux_pad (&srcaudiotemplate, "audio_%u", 0);
387 388 389 390 391
}

GST_END_TEST;


392
GST_START_TEST (test_video_pad_frag_dd)
393
{
394
  check_qtmux_pad_fragmented (&srcvideotemplate, "video_%u", 0, FALSE);
395 396 397 398
}

GST_END_TEST;

399
GST_START_TEST (test_audio_pad_frag_dd)
400
{
401
  check_qtmux_pad_fragmented (&srcaudiotemplate, "audio_%u", 0, FALSE);
402 403 404 405 406
}

GST_END_TEST;


407
GST_START_TEST (test_video_pad_frag_dd_streamable)
408
{
409
  check_qtmux_pad_fragmented (&srcvideotemplate, "video_%u", 0, TRUE);
410 411 412 413 414
}

GST_END_TEST;


415
GST_START_TEST (test_audio_pad_frag_dd_streamable)
416
{
417
  check_qtmux_pad_fragmented (&srcaudiotemplate, "audio_%u", 0, TRUE);
418 419 420 421
}

GST_END_TEST;

422 423 424 425
/* dts-method reorder */

GST_START_TEST (test_video_pad_reorder)
{
426
  check_qtmux_pad (&srcvideotemplate, "video_%u", 1);
427 428 429 430 431 432
}

GST_END_TEST;

GST_START_TEST (test_audio_pad_reorder)
{
433
  check_qtmux_pad (&srcaudiotemplate, "audio_%u", 1);
434 435 436 437 438 439 440
}

GST_END_TEST;


GST_START_TEST (test_video_pad_frag_reorder)
{
441
  check_qtmux_pad_fragmented (&srcvideotemplate, "video_%u", 1, FALSE);
442 443 444 445 446 447
}

GST_END_TEST;

GST_START_TEST (test_audio_pad_frag_reorder)
{
448
  check_qtmux_pad_fragmented (&srcaudiotemplate, "audio_%u", 1, FALSE);
449 450 451 452 453 454 455
}

GST_END_TEST;


GST_START_TEST (test_video_pad_frag_reorder_streamable)
{
456
  check_qtmux_pad_fragmented (&srcvideotemplate, "video_%u", 1, TRUE);
457 458 459 460 461 462 463
}

GST_END_TEST;


GST_START_TEST (test_audio_pad_frag_reorder_streamable)
{
464
  check_qtmux_pad_fragmented (&srcaudiotemplate, "audio_%u", 1, TRUE);
465 466 467 468 469 470 471 472
}

GST_END_TEST;

/* dts-method asc */

GST_START_TEST (test_video_pad_asc)
{
473
  check_qtmux_pad (&srcvideotemplate, "video_%u", 2);
474 475 476 477 478 479
}

GST_END_TEST;

GST_START_TEST (test_audio_pad_asc)
{
480
  check_qtmux_pad (&srcaudiotemplate, "audio_%u", 2);
481 482 483 484 485 486 487
}

GST_END_TEST;


GST_START_TEST (test_video_pad_frag_asc)
{
488
  check_qtmux_pad_fragmented (&srcvideotemplate, "video_%u", 2, FALSE);
489 490 491 492 493 494
}

GST_END_TEST;

GST_START_TEST (test_audio_pad_frag_asc)
{
495
  check_qtmux_pad_fragmented (&srcaudiotemplate, "audio_%u", 2, FALSE);
496 497 498 499 500 501 502
}

GST_END_TEST;


GST_START_TEST (test_video_pad_frag_asc_streamable)
{
503
  check_qtmux_pad_fragmented (&srcvideotemplate, "video_%u", 2, TRUE);
504 505 506 507 508 509 510
}

GST_END_TEST;


GST_START_TEST (test_audio_pad_frag_asc_streamable)
{
511
  check_qtmux_pad_fragmented (&srcaudiotemplate, "audio_%u", 2, TRUE);
512 513 514
}

GST_END_TEST;
515

Thiago Santos's avatar
Thiago Santos committed
516 517
GST_START_TEST (test_reuse)
{
518
  GstElement *qtmux = setup_qtmux (&srcvideotemplate, "video_%u");
Thiago Santos's avatar
Thiago Santos committed
519 520
  GstBuffer *inbuffer;
  GstCaps *caps;
521
  GstSegment segment;
Thiago Santos's avatar
Thiago Santos committed
522 523 524 525 526 527 528

  gst_element_set_state (qtmux, GST_STATE_PLAYING);
  gst_element_set_state (qtmux, GST_STATE_NULL);
  gst_element_set_state (qtmux, GST_STATE_PLAYING);
  gst_pad_set_active (mysrcpad, TRUE);
  gst_pad_set_active (mysinkpad, TRUE);

529 530 531 532 533 534
  gst_pad_push_event (mysrcpad, gst_event_new_stream_start ("test"));

  caps = gst_pad_get_pad_template_caps (mysrcpad);
  gst_pad_set_caps (mysrcpad, caps);
  gst_caps_unref (caps);

535 536 537 538
  /* ensure segment (format) properly setup */
  gst_segment_init (&segment, GST_FORMAT_TIME);
  fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)));

Thiago Santos's avatar
Thiago Santos committed
539 540
  inbuffer = gst_buffer_new_and_alloc (1);
  fail_unless (inbuffer != NULL);
541
  gst_buffer_memset (inbuffer, 0, 0, 1);
Thiago Santos's avatar
Thiago Santos committed
542 543 544 545 546 547 548 549
  GST_BUFFER_TIMESTAMP (inbuffer) = 0;
  GST_BUFFER_DURATION (inbuffer) = 40 * GST_MSECOND;
  ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);
  fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK);

  /* send eos to have all written */
  fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()) == TRUE);

550
  cleanup_qtmux (qtmux, "video_%u");
Thiago Santos's avatar
Thiago Santos committed
551 552 553 554
}

GST_END_TEST;

555 556 557 558 559 560 561
static GstEncodingContainerProfile *
create_qtmux_profile (const gchar * variant)
{
  GstEncodingContainerProfile *cprof;
  GstCaps *caps;

  if (variant == NULL) {
562
    caps = gst_caps_new_empty_simple ("video/quicktime");
563 564 565 566 567 568 569 570
  } else {
    caps = gst_caps_new_simple ("video/quicktime",
        "variant", G_TYPE_STRING, variant, NULL);
  }

  cprof = gst_encoding_container_profile_new ("Name", "blah", caps, NULL);
  gst_caps_unref (caps);

Wim Taymans's avatar
Wim Taymans committed
571 572 573
  caps = gst_caps_new_simple ("audio/x-raw",
      "format", G_TYPE_STRING, "S16BE",
      "channels", G_TYPE_INT, 2, "rate", G_TYPE_INT, 44100, NULL);
574 575 576 577 578 579 580 581
  gst_encoding_container_profile_add_profile (cprof,
      GST_ENCODING_PROFILE (gst_encoding_audio_profile_new (caps, NULL, NULL,
              1)));
  gst_caps_unref (caps);

  return cprof;
}

582
GST_START_TEST (test_encodebin_qtmux)
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
{
  GstEncodingContainerProfile *cprof;
  GstElement *enc;
  GstPad *pad;

  enc = gst_element_factory_make ("encodebin", NULL);
  if (enc == NULL)
    return;

  /* Make sure encodebin finds a muxer for a profile with a variant field .. */
  cprof = create_qtmux_profile ("apple");
  g_object_set (enc, "profile", cprof, NULL);
  gst_encoding_profile_unref (cprof);

  /* should have created a pad after setting the profile */
  pad = gst_element_get_static_pad (enc, "audio_0");
  fail_unless (pad != NULL);
  gst_object_unref (pad);
  gst_object_unref (enc);

  /* ... and for a profile without a variant field */
  enc = gst_element_factory_make ("encodebin", NULL);
  cprof = create_qtmux_profile (NULL);
  g_object_set (enc, "profile", cprof, NULL);
  gst_encoding_profile_unref (cprof);

  /* should have created a pad after setting the profile */
  pad = gst_element_get_static_pad (enc, "audio_0");
  fail_unless (pad != NULL);
  gst_object_unref (pad);
  gst_object_unref (enc);
}

GST_END_TEST;

618 619 620 621 622 623 624 625 626 627 628 629 630
/* Fake mp3 encoder for test */
typedef GstElement TestMp3Enc;
typedef GstElementClass TestMp3EncClass;

static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/mpeg, mpegversion=1, layer=[1,3]")
    );

static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
Wim Taymans's avatar
Wim Taymans committed
631
    GST_STATIC_CAPS ("audio/x-raw")
632 633 634 635
    );

static GType test_mp3_enc_get_type (void);

636
G_DEFINE_TYPE (TestMp3Enc, test_mp3_enc, GST_TYPE_ELEMENT);
637 638

static void
639
test_mp3_enc_class_init (TestMp3EncClass * klass)
640 641 642 643 644 645 646 647
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);

  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&sink_template));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&src_template));

648
  gst_element_class_set_metadata (element_class, "MPEG1 Audio Encoder",
649 650 651 652
      "Codec/Encoder/Audio", "Pretends to encode mp3", "Foo Bar <foo@bar.com>");
}

static void
653
test_mp3_enc_init (TestMp3Enc * mp3enc)
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705
{
  GstPad *pad;

  pad = gst_pad_new_from_static_template (&sink_template, "sink");
  gst_element_add_pad (mp3enc, pad);

  pad = gst_pad_new_from_static_template (&src_template, "src");
  gst_element_add_pad (mp3enc, pad);
}

static gboolean
plugin_init (GstPlugin * plugin)
{
  return gst_element_register (plugin, "testmp3enc", GST_RANK_NONE,
      test_mp3_enc_get_type ());
}

static GstEncodingContainerProfile *
create_mp4mux_profile (void)
{
  GstEncodingContainerProfile *cprof;
  GstCaps *caps;

  caps = gst_caps_new_simple ("video/quicktime",
      "variant", G_TYPE_STRING, "iso", NULL);

  cprof = gst_encoding_container_profile_new ("Name", "blah", caps, NULL);
  gst_caps_unref (caps);

  caps = gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, 1,
      "layer", G_TYPE_INT, 3, "channels", G_TYPE_INT, 2, "rate", G_TYPE_INT,
      44100, NULL);
  gst_encoding_container_profile_add_profile (cprof,
      GST_ENCODING_PROFILE (gst_encoding_audio_profile_new (caps, NULL, NULL,
              1)));
  gst_caps_unref (caps);

  return cprof;
}

GST_START_TEST (test_encodebin_mp4mux)
{
  GstEncodingContainerProfile *cprof;
  GstPluginFeature *feature;
  GstElement *enc, *mux;
  GstPad *pad;

  /* need a fake mp3 encoder because mp4 only accepts encoded formats */
  gst_plugin_register_static (GST_VERSION_MAJOR, GST_VERSION_MINOR,
      "fakemp3enc", "fakemp3enc", plugin_init, VERSION, "LGPL",
      "gst-plugins-good", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);

706
  feature = gst_registry_find_feature (gst_registry_get (), "testmp3enc",
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730
      GST_TYPE_ELEMENT_FACTORY);
  gst_plugin_feature_set_rank (feature, GST_RANK_PRIMARY + 100);

  enc = gst_element_factory_make ("encodebin", NULL);
  if (enc == NULL)
    return;

  /* Make sure encodebin finds mp4mux even though qtmux outputs a superset */
  cprof = create_mp4mux_profile ();
  g_object_set (enc, "profile", cprof, NULL);
  gst_encoding_profile_unref (cprof);

  /* should have created a pad after setting the profile */
  pad = gst_element_get_static_pad (enc, "audio_0");
  fail_unless (pad != NULL);
  gst_object_unref (pad);

  mux = gst_bin_get_by_interface (GST_BIN (enc), GST_TYPE_TAG_SETTER);
  fail_unless (mux != NULL);
  {
    GstElementFactory *f = gst_element_get_factory (mux);

    /* make sure we got mp4mux for variant=iso */
    GST_INFO ("muxer: %s", G_OBJECT_TYPE_NAME (mux));
731
    fail_unless_equals_string (GST_OBJECT_NAME (f), "mp4mux");
732 733 734 735 736 737 738 739 740 741
  }
  gst_object_unref (mux);
  gst_object_unref (enc);

  gst_plugin_feature_set_rank (feature, GST_RANK_NONE);
  gst_object_unref (feature);
}

GST_END_TEST;

742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758
static gboolean
extract_tags (const gchar * location, GstTagList ** taglist)
{
  gboolean ret = TRUE;
  GstElement *src;
  GstBus *bus;
  GstElement *pipeline =
      gst_parse_launch ("filesrc name=src ! qtdemux ! fakesink", NULL);

  src = gst_bin_get_by_name (GST_BIN (pipeline), "src");
  g_object_set (src, "location", location, NULL);

  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  fail_unless (gst_element_set_state (pipeline, GST_STATE_PLAYING)
      != GST_STATE_CHANGE_FAILURE);

  if (*taglist == NULL) {
759
    *taglist = gst_tag_list_new_empty ();
760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
  }

  while (1) {
    GstMessage *msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
        GST_MESSAGE_TAG | GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

    if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_EOS) {
      gst_message_unref (msg);
      break;
    } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
      ret = FALSE;
      gst_message_unref (msg);
      break;
    } else if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_TAG) {
      GstTagList *tags;

      gst_message_parse_tag (msg, &tags);
      gst_tag_list_insert (*taglist, tags, GST_TAG_MERGE_REPLACE);
778
      gst_tag_list_unref (tags);
779 780 781 782 783 784 785 786 787 788 789
    }
    gst_message_unref (msg);
  }

  gst_object_unref (bus);
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (src);
  gst_object_unref (pipeline);
  return ret;
}

790 791 792
static void
test_average_bitrate_custom (const gchar * elementname,
    GstStaticPadTemplate * tmpl, const gchar * sinkpadname)
793 794 795 796 797 798 799 800 801 802 803
{
  gchar *location;
  GstElement *qtmux;
  GstElement *filesink;
  GstBuffer *inbuffer;
  GstCaps *caps;
  int i;
  gint bytes[] = { 16, 22, 12 };
  gint64 durations[] = { GST_SECOND * 3, GST_SECOND * 5, GST_SECOND * 2 };
  gint64 total_bytes = 0;
  GstClockTime total_duration = 0;
804
  GstSegment segment;
805 806 807 808

  location = g_strdup_printf ("%s/%s-%d", g_get_tmp_dir (), "qtmuxtest",
      g_random_int ());
  GST_INFO ("Using location %s for bitrate test", location);
809
  qtmux = gst_check_setup_element (elementname);
810 811 812
  filesink = gst_element_factory_make ("filesink", NULL);
  g_object_set (filesink, "location", location, NULL);
  gst_element_link (qtmux, filesink);
813
  mysrcpad = setup_src_pad (qtmux, tmpl, sinkpadname);
814 815 816
  fail_unless (mysrcpad != NULL);
  gst_pad_set_active (mysrcpad, TRUE);

817

818 819 820 821 822 823 824
  fail_unless (gst_element_set_state (filesink,
          GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE,
      "could not set filesink to playing");
  fail_unless (gst_element_set_state (qtmux,
          GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
      "could not set to playing");

825 826 827 828 829 830
  gst_pad_push_event (mysrcpad, gst_event_new_stream_start ("test"));

  caps = gst_pad_get_pad_template_caps (mysrcpad);
  gst_pad_set_caps (mysrcpad, caps);
  gst_caps_unref (caps);

831 832 833 834
  /* ensure segment (format) properly setup */
  gst_segment_init (&segment, GST_FORMAT_TIME);
  fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment)));

835 836
  for (i = 0; i < 3; i++) {
    inbuffer = gst_buffer_new_and_alloc (bytes[i]);
837
    gst_buffer_memset (inbuffer, 0, 0, bytes[i]);
838 839 840 841
    GST_BUFFER_TIMESTAMP (inbuffer) = total_duration;
    GST_BUFFER_DURATION (inbuffer) = (GstClockTime) durations[i];
    ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1);

842
    total_bytes += gst_buffer_get_size (inbuffer);
843 844 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
    total_duration += GST_BUFFER_DURATION (inbuffer);
    fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK);
  }

  /* send eos to have moov written */
  fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()) == TRUE);

  gst_element_set_state (qtmux, GST_STATE_NULL);
  gst_element_set_state (filesink, GST_STATE_NULL);

  gst_pad_set_active (mysrcpad, FALSE);
  teardown_src_pad (mysrcpad);
  gst_object_unref (filesink);
  gst_check_teardown_element (qtmux);

  /* check the bitrate tag */
  {
    GstTagList *taglist = NULL;
    guint bitrate = 0;
    guint expected;

    fail_unless (extract_tags (location, &taglist));

    fail_unless (gst_tag_list_get_uint (taglist, GST_TAG_BITRATE, &bitrate));

    expected =
        (guint) gst_util_uint64_scale_round ((guint64) total_bytes,
        (guint64) 8 * GST_SECOND, (guint64) total_duration);
    fail_unless (bitrate == expected);
872
    gst_tag_list_unref (taglist);
873
  }
874 875 876 877

  /* delete file */
  g_unlink (location);
  g_free (location);
878 879
}

880 881
GST_START_TEST (test_average_bitrate)
{
882 883
  test_average_bitrate_custom ("mp4mux", &srcaudioaactemplate, "audio_%u");
  test_average_bitrate_custom ("mp4mux", &srcvideoh264template, "video_%u");
884

885 886
  test_average_bitrate_custom ("qtmux", &srcaudioaactemplate, "audio_%u");
  test_average_bitrate_custom ("qtmux", &srcvideoh264template, "video_%u");
887 888
}

889 890 891
GST_END_TEST;


892
static Suite *
893 894 895 896 897
qtmux_suite (void)
{
  Suite *s = suite_create ("qtmux");
  TCase *tc_chain = tcase_create ("general");

898 899 900
  /* avoid glib warnings when setting deprecated dts-method property */
  g_setenv ("G_ENABLE_DIAGNOSTIC", "0", TRUE);

901
  suite_add_tcase (s, tc_chain);
902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922
  tcase_add_test (tc_chain, test_video_pad_dd);
  tcase_add_test (tc_chain, test_audio_pad_dd);
  tcase_add_test (tc_chain, test_video_pad_frag_dd);
  tcase_add_test (tc_chain, test_audio_pad_frag_dd);
  tcase_add_test (tc_chain, test_video_pad_frag_dd_streamable);
  tcase_add_test (tc_chain, test_audio_pad_frag_dd_streamable);

  tcase_add_test (tc_chain, test_video_pad_reorder);
  tcase_add_test (tc_chain, test_audio_pad_reorder);
  tcase_add_test (tc_chain, test_video_pad_frag_reorder);
  tcase_add_test (tc_chain, test_audio_pad_frag_reorder);
  tcase_add_test (tc_chain, test_video_pad_frag_reorder_streamable);
  tcase_add_test (tc_chain, test_audio_pad_frag_reorder_streamable);

  tcase_add_test (tc_chain, test_video_pad_asc);
  tcase_add_test (tc_chain, test_audio_pad_asc);
  tcase_add_test (tc_chain, test_video_pad_frag_asc);
  tcase_add_test (tc_chain, test_audio_pad_frag_asc);
  tcase_add_test (tc_chain, test_video_pad_frag_asc_streamable);
  tcase_add_test (tc_chain, test_audio_pad_frag_asc_streamable);

923 924
  tcase_add_test (tc_chain, test_average_bitrate);

Thiago Santos's avatar
Thiago Santos committed
925
  tcase_add_test (tc_chain, test_reuse);
926 927
  tcase_add_test (tc_chain, test_encodebin_qtmux);
  tcase_add_test (tc_chain, test_encodebin_mp4mux);
Thiago Santos's avatar
Thiago Santos committed
928

929 930 931
  return s;
}

932
GST_CHECK_MAIN (qtmux)