cmmldec.c 16.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
 * cmmldec.c - GStreamer CMML decoder test suite
 * Copyright (C) 2005 Alessandro Decina
 * 
 * Authors:
 *   Alessandro Decina <alessandro@nnva.org>
 *
 * 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.
 */

#include <gst/check/gstcheck.h>
25
26
#include <gst/tag/tag.h>

27
#define SINK_CAPS "text/x-cmml"
28
29
30
31
32
33
#define SRC_CAPS "text/x-cmml"

#define IDENT_HEADER \
  "CMML\x00\x00\x00\x00"\
  "\x03\x00\x00\x00"\
  "\xe8\x03\x00\x00\x00\x00\x00\x00"\
34
  "\x01\x00\x00\x00\x00\x00\x00\x00"\
35
  "\x20"
36
#define IDENT_HEADER_SIZE 29
37

38
#define PREAMBLE_NO_PI \
39
  "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"\
40
41
42
  "<!DOCTYPE cmml SYSTEM \"cmml.dtd\">\n"
#define PREAMBLE PREAMBLE_NO_PI "<?cmml?>"
#define PREAMBLE_DECODED PREAMBLE_NO_PI "<cmml >"
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

#define HEAD_TAG \
  "<head>"\
  "<title>The Research Hunter</title>"\
  "<meta name=\"DC.audience\" content=\"General\"/>"\
  "<meta name=\"DC.author\" content=\"CSIRO Publishing\"/>"\
  "<meta name=\"DC.format\" content=\"video\"/>"\
  "<meta name=\"DC.language\" content=\"English\"/>"\
  "<meta name=\"DC.publisher\" content=\"CSIRO Australia\"/>"\
  "</head>"

#define HEAD_TAG_DECODED HEAD_TAG

#define CLIP_TEMPLATE \
  "<clip id=\"%s\" track=\"%s\">"\
  "<a href=\"http://www.csiro.au/\">http://www.csiro.au</a>"\
  "<img src=\"images/index1.jpg\"/>"\
  "<desc>Welcome to CSIRO</desc>"\
  "<meta name=\"test\" content=\"test content\"/>"\
  "</clip>"

#define CLIP_TEMPLATE_DECODED \
  "<clip id=\"%s\" track=\"%s\" start=\"%s\">"\
  "<a href=\"http://www.csiro.au/\">http://www.csiro.au</a>"\
  "<img src=\"images/index1.jpg\"/>"\
  "<desc>Welcome to CSIRO</desc>"\
  "<meta name=\"test\" content=\"test content\"/>"\
  "</clip>"

72
73
74
#define EMPTY_CLIP_TEMPLATE \
  "<clip id=\"%s\" track=\"%s\" />"

75
76
77
#define END_TAG \
  "</cmml>"

78
79
80
81
82
83
84
85
86
87
88
89
#define fail_unless_equals_flow_return(a, b)                            \
G_STMT_START {                                                          \
  gchar *a_up = g_ascii_strup (gst_flow_get_name (a), -1);              \
  gchar *b_up = g_ascii_strup (gst_flow_get_name (b), -1);              \
  fail_unless (a == b,                                                  \
      "'" #a "' (GST_FLOW_%s) is not equal to '" #b "' (GST_FLOW_%s)",  \
      a_up, b_up);                                                      \
  g_free (a_up);                                                        \
  g_free (b_up);                                                        \
} G_STMT_END;

static GstElement *cmmldec;
90

91
static GstBus *bus;
92

93
static GstFlowReturn flow;
94

95
GList *buffers;
96

97
static GList *current_buf;
98

99
static gint64 granulerate;
100

101
static guint8 granuleshift;
102

103
static GstPad *srcpad, *sinkpad;
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (SINK_CAPS)
    );

static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (SRC_CAPS)
    );

static GstBuffer *
buffer_new (const gchar * buffer_data, guint size)
{
  GstBuffer *buffer;
121

122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
  GstCaps *caps;

  buffer = gst_buffer_new_and_alloc (size);
  memcpy (GST_BUFFER_DATA (buffer), buffer_data, size);
  caps = gst_caps_from_string (SRC_CAPS);
  gst_buffer_set_caps (buffer, caps);
  gst_caps_unref (caps);

  return buffer;
}

static void
buffer_unref (void *buffer, void *user_data)
{
  ASSERT_OBJECT_REFCOUNT (buffer, "buf", 1);
  gst_buffer_unref (GST_BUFFER (buffer));
}

140
static void
141
142
143
144
145
146
setup_cmmldec ()
{
  GST_DEBUG ("setup_cmmldec");
  cmmldec = gst_check_setup_element ("cmmldec");
  srcpad = gst_check_setup_src_pad (cmmldec, &srctemplate, NULL);
  sinkpad = gst_check_setup_sink_pad (cmmldec, &sinktemplate, NULL);
147
148
  gst_pad_set_active (srcpad, TRUE);
  gst_pad_set_active (sinkpad, TRUE);
149
150
151
152
153
154
155
156
157
158
159
160
161
162

  bus = gst_bus_new ();
  gst_element_set_bus (cmmldec, bus);

  fail_unless (gst_element_set_state (cmmldec,
          GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE,
      "could not set to playing");

  granulerate = GST_SECOND / 1000;
  granuleshift = 32;
  buffers = NULL;
}

static void
163
teardown_cmmldec ()
164
165
166
{
  g_list_foreach (buffers, buffer_unref, NULL);
  g_list_free (buffers);
167
168
  buffers = NULL;
  current_buf = NULL;
169
170
171
172

  gst_bus_set_flushing (bus, TRUE);
  gst_object_unref (bus);

173
  GST_DEBUG ("teardown_cmmldec");
174
175
  gst_pad_set_active (srcpad, FALSE);
  gst_pad_set_active (sinkpad, FALSE);
176
177
178
179
180
181
182
183
184
  gst_check_teardown_src_pad (cmmldec);
  gst_check_teardown_sink_pad (cmmldec);
  gst_check_teardown_element (cmmldec);
}

static void
check_output_buffer_is_equal (const gchar * name,
    const gchar * data, gint refcount)
{
185
186
187
188
189
190
191
192
193
194
  GstBuffer *buffer;

  if (current_buf == NULL)
    current_buf = buffers;
  else
    current_buf = g_list_next (current_buf);

  fail_unless (current_buf != NULL);

  buffer = GST_BUFFER (current_buf->data);
195
196
197
198
199
200
201

  ASSERT_OBJECT_REFCOUNT (buffer, name, refcount);
  fail_unless (memcmp (GST_BUFFER_DATA (buffer), data,
          GST_BUFFER_SIZE (buffer)) == 0,
      "'%s' (%s) is not equal to (%s)", name, GST_BUFFER_DATA (buffer), data);
}

202
203
static GstFlowReturn
push_data (const gchar * name, const gchar * data, gint size, gint64 granulepos)
204
205
206
207
208
{
  GstBuffer *buffer;

  buffer = buffer_new (data, size);
  GST_BUFFER_OFFSET_END (buffer) = granulepos;
209
210
211
212
213
214
215
  return gst_pad_push (srcpad, buffer);
}

static GObject *
cmml_tag_message_pop (GstBus * bus, const gchar * tag)
{
  GstMessage *message;
216

217
  GstTagList *taglist;
218

219
  const GValue *value;
220

221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
  GObject *obj;

  message = gst_bus_poll (bus, GST_MESSAGE_TAG, 0);
  if (message == NULL)
    return NULL;

  gst_message_parse_tag (message, &taglist);
  value = gst_tag_list_get_value_index (taglist, tag, 0);
  if (value == NULL) {
    gst_message_unref (message);
    gst_tag_list_free (taglist);
    return NULL;
  }

  obj = g_value_dup_object (value);
  gst_message_unref (message);
  gst_tag_list_free (taglist);

  return obj;
240
241
242
243
244
}

static void
check_headers ()
{
245
  GObject *head_tag;
246

247
  gchar *title, *base;
248

249
250
  GValueArray *meta;

251
  /* push the ident header */
252
253
254
255
256
257
258
  flow = push_data ("ident-header", IDENT_HEADER, IDENT_HEADER_SIZE, 0);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);

  /* push the cmml preamble */
  flow = push_data ("preamble", PREAMBLE, strlen (PREAMBLE), 0);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);

259
  /* push the head tag */
260
261
  flow = push_data ("head", HEAD_TAG, strlen (HEAD_TAG), 0);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
262

263
  fail_unless_equals_int (g_list_length (buffers), 2);
264

265
  /* check the decoded preamble */
266
267
268
269
  check_output_buffer_is_equal ("cmml-preamble-buffer", PREAMBLE_DECODED, 1);

  /* check the decoded head tag */
  check_output_buffer_is_equal ("head-tag-buffer", HEAD_TAG_DECODED, 1);
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284

  /* check the GstCmmlTagHead tag object */
  head_tag = cmml_tag_message_pop (bus, GST_TAG_CMML_HEAD);
  fail_unless (head_tag != NULL);
  g_object_get (head_tag,
      "title", &title, "base-uri", &base, "meta", &meta, NULL);
  fail_unless_equals_string ("The Research Hunter", title);
  fail_unless (base == NULL);
  fail_unless (meta != NULL);
  fail_unless_equals_int (meta->n_values, 10);

  g_free (title);
  g_free (base);
  g_value_array_free (meta);
  g_object_unref (head_tag);
285
286
}

287
288
289
static GstFlowReturn
push_clip_full (const gchar * name, const gchar * track, const gchar * template,
    GstClockTime prev, GstClockTime start)
290
291
{
  gchar *clip;
292

293
  gint64 keyindex, keyoffset, granulepos;
294

295
  GstFlowReturn res;
296
297
298
299

  if (track == NULL)
    track = "default";

300
301
302
  if (prev == GST_CLOCK_TIME_NONE)
    prev = 0;

303
304
305
306
  keyindex = prev / granulerate << granuleshift;
  keyoffset = (start - prev) / granulerate;
  granulepos = keyindex + keyoffset;

307
308
  clip = g_strdup_printf (template, name, track);
  res = push_data (name, clip, strlen (clip), granulepos);
309
  g_free (clip);
310
311

  return res;
312
313
}

314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
static GstFlowReturn
push_clip (const gchar * name, const gchar * track,
    GstClockTime prev, GstClockTime start)
{
  return push_clip_full (name, track, CLIP_TEMPLATE, prev, start);
}

static GstFlowReturn
push_empty_clip (const gchar * name, const gchar * track, GstClockTime start)
{
  return push_clip_full (name, track,
      EMPTY_CLIP_TEMPLATE, GST_CLOCK_TIME_NONE, start);
}


329
static void
330
check_output_clip (const gchar * name, const gchar * track,
331
332
333
334
335
336
337
338
339
340
341
342
343
344
    const gchar * start, const gchar * end)
{
  gchar *decoded_clip;

  if (track == NULL)
    track = "default";

  decoded_clip = g_strdup_printf (CLIP_TEMPLATE_DECODED, name, track, start);
  check_output_buffer_is_equal (name, decoded_clip, 1);
  g_free (decoded_clip);
}

GST_START_TEST (test_dec)
{
345
  GstClockTime clip1_start = 1 * GST_SECOND + 234 * GST_MSECOND;
346

347
  GstClockTime clip2_start = clip1_start;
348

349
350
  GstClockTime clip3_start =
      ((100 * 3600) + (59 * 60) + 59) * GST_SECOND + 678 * GST_MSECOND;
351
352
353

  check_headers ();

354
355
356
357
358
359
360
361
362
  flow = push_clip ("clip-1", "default", GST_CLOCK_TIME_NONE, clip1_start);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);

  flow = push_clip ("clip-2", "othertrack", GST_CLOCK_TIME_NONE, clip2_start);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);

  flow = push_clip ("clip-3", "default", clip1_start, clip3_start);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);

363
364
365
  /* send EOS to flush clip-2 and clip-3 */
  gst_pad_send_event (GST_PAD_PEER (srcpad), gst_event_new_eos ());

366
367
368
369
370
371
372
  check_output_clip ("clip-1", "default", "0:00:01.234", NULL);
  check_output_clip ("clip-2", "othertrack", "0:00:01.234", NULL);
  check_output_clip ("clip-3", "default", "100:59:59.678", NULL);
  check_output_buffer_is_equal ("cmml-end-tag", END_TAG, 1);
}

GST_END_TEST;
373

374
375
376
377
378
379
380
381
382
383
384
385
386
GST_START_TEST (test_preamble_no_pi)
{
  flow = push_data ("ident-header", IDENT_HEADER, IDENT_HEADER_SIZE, 0);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
  fail_unless_equals_int (g_list_length (buffers), 0);

  flow = push_data ("preamble-no-pi",
      PREAMBLE_NO_PI, strlen (PREAMBLE_NO_PI), 0);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
  fail_unless_equals_int (g_list_length (buffers), 1);

  check_output_buffer_is_equal ("cmml-preamble-buffer",
      PREAMBLE_NO_PI "<cmml>", 1);
387
388
389
390
391
392
393
}

GST_END_TEST;

GST_START_TEST (test_tags)
{
  GObject *tag;
394

395
  gboolean empty;
396

397
  gchar *id, *track;
398

399
  gint64 start_time, end_time;
400

401
  gchar *anchor_href, *anchor_text;
402

403
  gchar *img_src, *img_alt;
404

405
  gchar *desc;
406

407
  GValueArray *meta;
408

409
  GstClockTime clip1_start;
410
411
412

  check_headers ();

413
414
415
  clip1_start = 1 * GST_SECOND + 234 * GST_MSECOND;
  flow = push_clip ("clip-1", "default", 0, clip1_start);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
416

417
  tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP);
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
  fail_unless (tag != NULL);

  g_object_get (tag, "id", &id, "empty", &empty, "track", &track,
      "start-time", &start_time, "end-time", &end_time,
      "anchor-uri", &anchor_href, "anchor-text", &anchor_text,
      "img-uri", &img_src, "img-alt", &img_alt,
      "description", &desc, "meta", &meta, NULL);

  fail_unless (empty == FALSE);
  fail_unless_equals_string (id, "clip-1");
  fail_unless_equals_string (track, "default");
  fail_unless_equals_int (start_time, 1 * GST_SECOND + 234 * GST_MSECOND);
  fail_unless_equals_uint64 (end_time, GST_CLOCK_TIME_NONE);
  fail_unless_equals_string (anchor_href, "http://www.csiro.au/");
  fail_unless_equals_string (anchor_text, "http://www.csiro.au");
  fail_unless_equals_string (img_src, "images/index1.jpg");
  fail_unless (img_alt == NULL);
  fail_unless_equals_string (desc, "Welcome to CSIRO");
  fail_unless (meta != NULL);
  fail_unless_equals_int (meta->n_values, 2);

  g_free (id);
  g_free (track);
  g_free (anchor_href);
  g_free (anchor_text);
  g_free (img_src);
  g_free (img_alt);
  g_free (desc);
  g_value_array_free (meta);
447
  g_object_unref (tag);
448
449
450
451
}

GST_END_TEST;

452
GST_START_TEST (test_wait_clip_end)
453
{
454
  GObject *tag;
455

456
  gchar *id;
457

458
  GstClockTime end_time = 0;
459
460

  GstClockTime clip1_start = 1 * GST_SECOND + 234 * GST_MSECOND;
461

462
  GstClockTime clip2_start = 2 * GST_SECOND + 234 * GST_MSECOND;
463

464
  GstClockTime clip3_start = 3 * GST_SECOND + 234 * GST_MSECOND;
465

466
  GstClockTime clip3_end = 4 * GST_SECOND + 234 * GST_MSECOND;
467

468
469
  GstClockTime clip4_start = 5 * GST_SECOND + 234 * GST_MSECOND;

470
471
472
473
  g_object_set (cmmldec, "wait-clip-end-time", TRUE, NULL);

  check_headers ();

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
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
  flow = push_clip ("clip-1", "default", 0, clip1_start);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
  /* no tag has been posted yet */
  fail_unless (cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP) == NULL);

  flow = push_clip ("clip-2", "default", clip1_start, clip2_start);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);

  tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP);
  fail_unless (tag != NULL);
  g_object_get (tag, "id", &id, "end-time", &end_time, NULL);
  /* clip-1 is posted when clip-2 is decoded. clip-1 ends when clip-2 starts */
  fail_unless_equals_string (id, "clip-1");
  fail_unless_equals_int (end_time, clip2_start);
  g_free (id);
  g_object_unref (tag);

  flow = push_clip ("clip-3", "default", clip2_start, clip3_start);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);

  tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP);
  fail_unless (tag != NULL);
  g_object_get (tag, "id", &id, "end-time", &end_time, NULL);
  /* clip-2 is posted when clip-3 is decoded. It ends when clip-3 starts */
  fail_unless_equals_string (id, "clip-2");
  fail_unless_equals_int (end_time, clip3_start);
  g_free (id);
  g_object_unref (tag);

  flow = push_empty_clip ("empty-clip", "default", clip3_end);
  tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP);
  fail_unless (tag != NULL);
  g_object_get (tag, "id", &id, "end-time", &end_time, NULL);
  /* clip-3 ends when empty-clip is decoded */
  fail_unless_equals_string (id, "clip-3");
  fail_unless_equals_int (end_time, clip3_end);
  g_free (id);
  g_object_unref (tag);

  flow = push_clip ("clip-4", "default", clip3_start, clip4_start);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);

  /* an empty clip just marks the end of the previous one, so no tag is posted
   * for empty-clip */
  fail_unless (cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP) == NULL);
  /* send EOS to flush clip-4 */
  gst_pad_send_event (GST_PAD_PEER (srcpad), gst_event_new_eos ());

  tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP);
  fail_unless (tag != NULL);
  g_object_get (tag, "id", &id, NULL);
  fail_unless_equals_string (id, "clip-4");
  g_free (id);
  g_object_unref (tag);
}

GST_END_TEST;

GST_START_TEST (test_weird_input)
{
  const gchar *bad_xml = "<?xml version=\"1.0\"?><a><b></a>";

  /* malformed ident header */
  flow = push_data ("bad-ident-header", "CMML\0\0\0\0garbage", 15, 0);
  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR);

  /* push invalid xml */
  flow = push_data ("bad-xml", bad_xml, strlen (bad_xml), 0);
  fail_unless_equals_flow_return (flow, GST_FLOW_ERROR);

  /* and now for something completely different: an empty buffer. This is valid
   * as 'NIL' EOS pages are allowed */
  flow = push_data ("empty-eos", NULL, 0, 0);
  fail_unless_equals_flow_return (flow, GST_FLOW_OK);
548
549
}

550
551
552
GST_END_TEST;

GST_START_TEST (test_sink_query_convert)
553
{
554
  guint64 keyindex, keyoffset, granulepos;
555

556
  GstClockTime index_time, offset_time;
557

558
  GstFormat dstfmt = GST_FORMAT_TIME;
559

560
  gint64 dstval;
561

562
563
  /* send headers to set the granulerate */
  check_headers ();
564

565
566
567
568
569
570
571
  /* create a 1|1 granulepos */
  index_time = 1 * GST_SECOND;
  offset_time = 1 * GST_SECOND;

  keyindex = (index_time / granulerate) << granuleshift;
  keyoffset = offset_time / granulerate;
  granulepos = keyindex + keyoffset;
572

573
574
  fail_unless (gst_pad_query_convert (GST_PAD_PEER (srcpad),
          GST_FORMAT_DEFAULT, granulepos, &dstfmt, &dstval));
575

576
577
578
  fail_unless (dstfmt == GST_FORMAT_TIME);
  /* fail unless dstval == index + offset */
  fail_unless_equals_int (2 * GST_SECOND, dstval);
579
}
580
581
582
583
584
585
586

GST_END_TEST;

Suite *
cmmldec_suite ()
{
  Suite *s = suite_create ("cmmldec");
587

588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
  TCase *tc_general = tcase_create ("general");

  suite_add_tcase (s, tc_general);
  tcase_add_checked_fixture (tc_general, setup_cmmldec, teardown_cmmldec);
  tcase_add_test (tc_general, test_dec);
  tcase_add_test (tc_general, test_tags);
  tcase_add_test (tc_general, test_preamble_no_pi);
  tcase_add_test (tc_general, test_wait_clip_end);
  tcase_add_test (tc_general, test_sink_query_convert);
  tcase_add_test (tc_general, test_weird_input);

  return s;
}

GST_CHECK_MAIN (cmmldec);