matroska-mux: migrate test to gst_harness
[platform/upstream/gst-plugins-good.git] / tests / check / elements / matroskamux.c
1 /* GStreamer
2  *
3  * unit test for matroskamux
4  *
5  * Copyright (C) <2005> Michal Benes <michal.benes@xeris.cz>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #include <unistd.h>
24
25 #include <gst/check/gstcheck.h>
26 #include <gst/base/gstadapter.h>
27 #include <gst/check/gstharness.h>
28
29 #define AC3_CAPS_STRING "audio/x-ac3, " \
30                         "channels = (int) 1, " \
31                         "rate = (int) 8000"
32 #define VORBIS_TMPL_CAPS_STRING "audio/x-vorbis, " \
33                                 "channels = (int) 1, " \
34                                 "rate = (int) 8000, " \
35                                 "streamheader=(buffer)<10, 2020, 303030>"
36
37 static GstHarness *
38 setup_matroskamux_harness (const gchar * src_pad_str)
39 {
40   GstHarness *h;
41
42   h = gst_harness_new_with_padnames ("matroskamux", "audio_%u", "src");
43   gst_harness_set_src_caps_str (h, src_pad_str);
44   gst_harness_set_sink_caps_str (h, "video/x-matroska; audio/x-matroska");
45
46   return h;
47 }
48
49 static gboolean
50 seekable_sinkpad_query (GstPad * pad, GstObject * parent, GstQuery * query)
51 {
52   gboolean ret = FALSE;
53
54   if (GST_QUERY_TYPE (query) == GST_QUERY_SEEKING) {
55     gst_query_set_seeking (query, GST_FORMAT_BYTES, TRUE, 0, -1);
56     ret = TRUE;
57   }
58
59   return ret;
60 }
61
62 #define compare_buffer_to_data(buffer, data, data_size)             \
63 G_STMT_START {                                                      \
64 fail_unless_equals_int (data_size, gst_buffer_get_size (buffer));   \
65 fail_unless (gst_buffer_memcmp (buffer, 0, data, data_size) == 0);  \
66 } G_STMT_END
67
68 static void
69 test_ebml_header_with_version (gint version,
70     gconstpointer data, gsize data_size)
71 {
72   GstHarness *h;
73   GstBuffer *inbuffer, *outbuffer;
74
75   h = setup_matroskamux_harness (AC3_CAPS_STRING);
76   g_object_set (h->element, "version", version, NULL);
77
78   inbuffer = gst_harness_create_buffer (h, 1);
79   fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, inbuffer));
80   fail_unless_equals_int (2, gst_harness_buffers_received (h));
81
82   outbuffer = gst_harness_pull (h);
83   compare_buffer_to_data (outbuffer, data, data_size);
84   gst_buffer_unref (outbuffer);
85
86   gst_harness_teardown (h);
87 }
88
89 GST_START_TEST (test_ebml_header_v1)
90 {
91   guint8 data_v1[] = {
92     0x1a, 0x45, 0xdf, 0xa3,     /* master ID */
93     0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
94     0x42, 0x82,                 /* doctype */
95     0x89,                       /* 9 bytes */
96     0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61, 0x00,       /* "matroska" */
97     0x42, 0x87,                 /* doctypeversion */
98     0x81,                       /* 1 byte */
99     0x01,                       /* 1 */
100     0x42, 0x85,                 /* doctypereadversion */
101     0x81,                       /* 1 byte */
102     0x01,                       /* 1 */
103   };
104
105   test_ebml_header_with_version (1, data_v1, sizeof (data_v1));
106 }
107
108 GST_END_TEST;
109
110 GST_START_TEST (test_ebml_header_v2)
111 {
112   guint8 data_v2[] = {
113     0x1a, 0x45, 0xdf, 0xa3,     /* master ID */
114     0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
115     0x42, 0x82,                 /* doctype */
116     0x89,                       /* 9 bytes */
117     0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61, 0x00,       /* "matroska" */
118     0x42, 0x87,                 /* doctypeversion */
119     0x81,                       /* 1 byte */
120     0x02,                       /* 2 */
121     0x42, 0x85,                 /* doctypereadversion */
122     0x81,                       /* 1 byte */
123     0x02,                       /* 2 */
124   };
125
126   test_ebml_header_with_version (2, data_v2, sizeof (data_v2));
127 }
128
129 GST_END_TEST;
130
131
132 GST_START_TEST (test_vorbis_header)
133 {
134   GstHarness *h;
135   GstBuffer *inbuffer, *outbuffer;
136   gboolean vorbis_header_found = FALSE;
137   gint j;
138   gsize buffer_size;
139   guint8 data[] =
140       { 0x63, 0xa2, 0x89, 0x02, 0x01, 0x02, 0x10, 0x20, 0x20, 0x30, 0x30,
141     0x30
142   };
143
144   h = setup_matroskamux_harness (VORBIS_TMPL_CAPS_STRING);
145
146   inbuffer = gst_harness_create_buffer (h, 1);
147   fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, inbuffer));
148
149   outbuffer = gst_harness_pull (h);
150   while (outbuffer != NULL) {
151     buffer_size = gst_buffer_get_size (outbuffer);
152
153     if (!vorbis_header_found && buffer_size >= sizeof (data)) {
154       for (j = 0; j <= buffer_size - sizeof (data); j++) {
155         if (gst_buffer_memcmp (outbuffer, j, data, sizeof (data)) == 0) {
156           vorbis_header_found = TRUE;
157           break;
158         }
159       }
160     }
161
162     ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1);
163     gst_buffer_unref (outbuffer);
164
165     outbuffer = gst_harness_try_pull (h);
166   }
167
168   fail_unless (vorbis_header_found);
169
170   gst_harness_teardown (h);
171 }
172
173 GST_END_TEST;
174
175
176 static void
177 test_block_group_with_version (gint version,
178     gconstpointer data0, gsize data0_size)
179 {
180   GstHarness *h;
181   GstBuffer *inbuffer, *outbuffer;
182   guint8 data1[] = { 0x42 };
183
184   h = setup_matroskamux_harness (AC3_CAPS_STRING);
185   g_object_set (h->element, "version", version, NULL);
186
187   /* Generate the header */
188   inbuffer = gst_harness_create_buffer (h, 1);
189   GST_BUFFER_TIMESTAMP (inbuffer) = 0;
190   fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, inbuffer));
191   fail_unless_equals_int (5, gst_harness_buffers_received (h));
192
193   outbuffer = gst_harness_pull (h);
194   fail_unless (outbuffer != NULL);
195   while (outbuffer != NULL) {
196     gst_buffer_unref (outbuffer);
197     outbuffer = gst_harness_try_pull (h);
198   }
199
200   /* Now push a buffer */
201   inbuffer = gst_harness_create_buffer (h, 1);
202   gst_buffer_fill (inbuffer, 0, data1, sizeof (data1));
203   GST_BUFFER_TIMESTAMP (inbuffer) = 1000000;
204
205   fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, inbuffer));
206
207   outbuffer = gst_harness_pull (h);
208   compare_buffer_to_data (outbuffer, data0, data0_size);
209   gst_buffer_unref (outbuffer);
210
211   outbuffer = gst_harness_pull (h);
212   compare_buffer_to_data (outbuffer, data1, sizeof (data1));
213   gst_buffer_unref (outbuffer);
214
215   gst_harness_teardown (h);
216 }
217
218 GST_START_TEST (test_block_group_v1)
219 {
220   guint8 data0_v1[] = { 0xa0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
221     0xa1, 0x85,
222     0x81, 0x00, 0x01, 0x00
223   };
224
225   test_block_group_with_version (1, data0_v1, sizeof (data0_v1));
226 }
227
228 GST_END_TEST;
229
230 GST_START_TEST (test_block_group_v2)
231 {
232   guint8 data0_v2[] = { 0xa3, 0x85, 0x81, 0x00, 0x01, 0x00 };
233
234   test_block_group_with_version (2, data0_v2, sizeof (data0_v2));
235 }
236
237 GST_END_TEST;
238
239 GST_START_TEST (test_reset)
240 {
241   GstHarness *h;
242   GstBuffer *inbuffer;
243   GstBuffer *outbuffer;
244
245   h = setup_matroskamux_harness (AC3_CAPS_STRING);
246
247   inbuffer = gst_harness_create_buffer (h, 1);
248   GST_BUFFER_TIMESTAMP (inbuffer) = 0;
249   fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, inbuffer));
250   fail_unless_equals_int (5, gst_harness_buffers_received (h));
251
252   outbuffer = gst_harness_pull (h);
253   fail_unless (outbuffer != NULL);
254   while (outbuffer != NULL) {
255     gst_buffer_unref (outbuffer);
256     outbuffer = gst_harness_try_pull (h);
257   }
258
259   fail_unless_equals_int (GST_STATE_CHANGE_SUCCESS,
260       gst_element_set_state (h->element, GST_STATE_NULL));
261
262   gst_harness_play (h);
263
264   inbuffer = gst_harness_create_buffer (h, 1);
265   GST_BUFFER_TIMESTAMP (inbuffer) = 0;
266   fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, inbuffer));
267
268   outbuffer = gst_harness_pull (h);
269   fail_unless (outbuffer != NULL);
270   while (outbuffer != NULL) {
271     gst_buffer_unref (outbuffer);
272     outbuffer = gst_harness_try_pull (h);
273   }
274
275   gst_harness_teardown (h);
276 }
277
278 GST_END_TEST;
279
280 GST_START_TEST (test_link_webmmux_webm_sink)
281 {
282   GstHarness *h;
283
284   h = gst_harness_new_with_padnames ("webmmux", "audio_%u", "src");
285   fail_unless (h != NULL);
286
287   gst_harness_set_sink_caps_str (h, "video/webm; audio/webm");
288
289   gst_harness_play (h);
290
291   fail_unless_equals_int (GST_STATE_CHANGE_SUCCESS,
292       gst_element_set_state (h->element, GST_STATE_NULL));
293
294   gst_harness_teardown (h);
295 }
296
297 GST_END_TEST;
298
299 static gint64 timecodescales[] = {
300   GST_USECOND,
301   GST_MSECOND,
302   GST_MSECOND * 10,
303   GST_MSECOND * 100,
304   GST_MSECOND * 400,
305   /* FAILS: ? GST_MSECOND * 500, a bug? */
306 };
307
308 GST_START_TEST (test_timecodescale)
309 {
310   GstBuffer *inbuffer, *outbuffer;
311   guint8 data_h0[] = {
312     0xa3, 0x85, 0x81, 0x00, 0x00, 0x00,
313   };
314   guint8 data_h1[] = {
315     0xa3, 0x85, 0x81, 0x00, 0x01, 0x00,
316   };
317
318   GstHarness *h = setup_matroskamux_harness (AC3_CAPS_STRING);
319   gint64 timecodescale = timecodescales[__i__];
320
321   g_object_set (h->element, "timecodescale", timecodescale, NULL);
322   g_object_set (h->element, "version", 2, NULL);
323
324   /* Buffer 0 */
325   inbuffer = gst_harness_create_buffer (h, 1);
326   GST_BUFFER_TIMESTAMP (inbuffer) = 0;
327   fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, inbuffer));
328
329   /* pull out headers */
330   gst_buffer_unref (gst_harness_pull (h));
331   gst_buffer_unref (gst_harness_pull (h));
332   gst_buffer_unref (gst_harness_pull (h));
333
334   /* verify header and drop the data */
335   outbuffer = gst_harness_pull (h);
336   compare_buffer_to_data (outbuffer, data_h0, sizeof (data_h0));
337   gst_buffer_unref (outbuffer);
338   gst_buffer_unref (gst_harness_pull (h));
339
340   /* Buffer 1 */
341   inbuffer = gst_harness_create_buffer (h, 1);
342   GST_BUFFER_TIMESTAMP (inbuffer) = timecodescale;
343   fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, inbuffer));
344
345   /* verify header and drop the data */
346   outbuffer = gst_harness_pull (h);
347   compare_buffer_to_data (outbuffer, data_h1, sizeof (data_h1));
348   gst_buffer_unref (outbuffer);
349   gst_buffer_unref (gst_harness_pull (h));
350 }
351
352 GST_END_TEST;
353
354 /* Create a new chapter */
355 static GstTocEntry *
356 new_chapter (const guint chapter_nb, const gint64 start, const gint64 stop)
357 {
358   GstTocEntry *toc_entry, *toc_sub_entry;
359   GstTagList *tags;
360   gchar title[32];
361   gchar artist[32];
362   gchar str_uid[32];
363
364   g_snprintf (str_uid, sizeof (str_uid), "uid.%d", chapter_nb);
365   toc_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, str_uid);
366   gst_toc_entry_set_start_stop_times (toc_entry, start, stop);
367
368   g_snprintf (title, sizeof (title), "chap.%d", chapter_nb);
369   g_snprintf (artist, sizeof (artist), "art.%d", chapter_nb);
370   tags = gst_tag_list_new (GST_TAG_TITLE, title, GST_TAG_ARTIST, artist, NULL);
371   gst_toc_entry_set_tags (toc_entry, tags);
372
373   g_snprintf (str_uid, sizeof (str_uid), "uid.%d.1", chapter_nb);
374   toc_sub_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, str_uid);
375   gst_toc_entry_set_start_stop_times (toc_sub_entry, start, (start + stop) / 2);
376
377   g_snprintf (title, sizeof (title), "nested.%d.1", chapter_nb);
378   g_snprintf (artist, sizeof (artist), "art.%d.1", chapter_nb);
379   tags = gst_tag_list_new (GST_TAG_TITLE, title, GST_TAG_ARTIST, artist, NULL);
380   gst_toc_entry_set_tags (toc_sub_entry, tags);
381
382   gst_toc_entry_append_sub_entry (toc_entry, toc_sub_entry);
383
384   g_snprintf (str_uid, sizeof (str_uid), "uid.%d.2", chapter_nb);
385   toc_sub_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, str_uid);
386   gst_toc_entry_set_start_stop_times (toc_sub_entry, (start + stop) / 2, stop);
387
388   g_snprintf (title, sizeof (title), "nested/%d.2", chapter_nb);
389   g_snprintf (artist, sizeof (artist), "art.%d.2", chapter_nb);
390   tags = gst_tag_list_new (GST_TAG_TITLE, title, GST_TAG_ARTIST, artist, NULL);
391   gst_toc_entry_set_tags (toc_sub_entry, tags);
392
393   gst_toc_entry_append_sub_entry (toc_entry, toc_sub_entry);
394
395   return toc_entry;
396 }
397
398 /* Create a reference toc which includes a master edition entry */
399 static GstToc *
400 new_reference_toc (void)
401 {
402   GstToc *ref_toc;
403   GstTocEntry *toc_edition_entry, *toc_entry;
404   GstTagList *tags;
405
406   ref_toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL);
407
408   toc_edition_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, "00");
409   tags = gst_tag_list_new (GST_TAG_COMMENT, "Ed", NULL);
410   gst_toc_entry_set_tags (toc_edition_entry, tags);
411
412   toc_entry = new_chapter (1, 0 * GST_MSECOND, 2 * GST_MSECOND);
413   gst_toc_entry_append_sub_entry (toc_edition_entry, toc_entry);
414
415   toc_entry = new_chapter (2, 2 * GST_MSECOND, 4 * GST_MSECOND);
416   gst_toc_entry_append_sub_entry (toc_edition_entry, toc_entry);
417
418   gst_toc_append_entry (ref_toc, toc_edition_entry);
419
420   return ref_toc;
421 }
422
423 /* Create a toc which includes chapters without edition entry */
424 static GstToc *
425 new_no_edition_toc (void)
426 {
427   GstToc *ref_toc;
428   GstTocEntry *toc_entry;
429
430   ref_toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL);
431
432   toc_entry = new_chapter (1, 0 * GST_MSECOND, 2 * GST_MSECOND);
433   gst_toc_append_entry (ref_toc, toc_entry);
434
435   toc_entry = new_chapter (2, 2 * GST_MSECOND, 4 * GST_MSECOND);
436   gst_toc_append_entry (ref_toc, toc_entry);
437
438   return ref_toc;
439 }
440
441 static guint64
442 read_integer (GstMapInfo * info, gsize * index, guint64 len)
443 {
444   guint64 total = 0;
445
446   for (; len > 0; --len) {
447     total = (total << 8) | GST_READ_UINT8 (info->data + *index);
448     ++(*index);
449   }
450
451   return total;
452 }
453
454 static guint64
455 read_length (GstMapInfo * info, gsize * index)
456 {
457   gint len_mask = 0x80, read = 1;
458   guint64 total;
459   guint8 b;
460
461   b = GST_READ_UINT8 (info->data + *index);
462   ++(*index);
463   total = (guint64) b;
464   while (read <= 8 && !(total & len_mask)) {
465     read++;
466     len_mask >>= 1;
467   }
468   total &= (len_mask - 1);
469
470   for (; read > 1; --read) {
471     total = (total << 8) | GST_READ_UINT8 (info->data + *index);
472     ++(*index);
473   }
474
475   return total;
476 }
477
478 static gboolean
479 check_id (GstMapInfo * info, gsize * index,
480     guint8 * tag, gint tag_len, guint64 * len)
481 {
482   if (memcmp (info->data + *index, tag, tag_len) == 0) {
483     *index += tag_len;
484     *len = read_length (info, index);
485     return TRUE;
486   } else {
487     return FALSE;
488   }
489 }
490
491 static gboolean
492 check_id_read_int (GstMapInfo * info, gsize * index,
493     guint8 * tag, gint tag_len, guint64 * value)
494 {
495   guint64 len;
496
497   if (check_id (info, index, tag, tag_len, &len)) {
498     *value = read_integer (info, index, len);
499     return TRUE;
500   } else {
501     return FALSE;
502   }
503 }
504
505 /* Check the toc entry against the muxed buffer
506  * Returns the internal UID */
507 static void
508 check_chapter (GstTocEntry * toc_entry, GstTocEntry * internal_toc_entry,
509     GstMapInfo * info, gsize * index, gint last_offset)
510 {
511   guint64 len, value, uid;
512   gint64 start_ref, end_ref;
513   gchar s_uid[32];
514   const gchar *str_uid;
515   GstTocEntry *internal_chapter;
516   GList *cur_sub_chap;
517   GstTagList *tags;
518   gchar *title;
519
520   guint8 chapter_atom[] = { 0xb6 };
521   guint8 chapter_uid[] = { 0x73, 0xc4 };
522   guint8 chapter_str_uid[] = { 0x56, 0x54 };
523   guint8 chapter_start[] = { 0x91 };
524   guint8 chapter_end[] = { 0x92 };
525   guint8 chapter_flag_hidden[] = { 0x98 };
526   guint8 chapter_flag_enabled[] = { 0x45, 0x98 };
527   guint8 chapter_segment_uid[] = { 0x6e, 0x67 };
528   guint8 chapter_segment_edition_uid[] = { 0x6e, 0xbc };
529   guint8 chapter_physical_equiv[] = { 0x63, 0xc3 };
530   guint8 chapter_track[] = { 0x8f };
531   guint8 chapter_track_nb[] = { 0x89 };
532   guint8 chapter_display[] = { 0x80 };
533   guint8 chapter_string[] = { 0x85 };
534   guint8 chapter_language[] = { 0x43, 0x7c };
535
536   fail_unless (check_id (info, index, chapter_atom,
537           sizeof (chapter_atom), &len));
538
539   fail_unless (check_id_read_int (info, index, chapter_uid,
540           sizeof (chapter_uid), &uid));
541
542   /* optional StringUID */
543   if (check_id (info, index, chapter_str_uid, sizeof (chapter_str_uid), &len)) {
544     str_uid = gst_toc_entry_get_uid (toc_entry);
545     fail_unless (memcmp (info->data + *index, str_uid, strlen (str_uid)) == 0);
546     *index += len;
547   }
548
549   gst_toc_entry_get_start_stop_times (toc_entry, &start_ref, &end_ref);
550
551   fail_unless (check_id_read_int (info, index, chapter_start,
552           sizeof (chapter_start), &value));
553   fail_unless_equals_int (start_ref, value);
554
555   /* optional chapter end */
556   if (check_id_read_int (info, index, chapter_end,
557           sizeof (chapter_end), &value)) {
558     fail_unless_equals_int (end_ref, value);
559   }
560
561   fail_unless (check_id_read_int (info, index, chapter_flag_hidden,
562           sizeof (chapter_flag_hidden), &value));
563
564   fail_unless (check_id_read_int (info, index, chapter_flag_enabled,
565           sizeof (chapter_flag_enabled), &value));
566
567   /* optional segment UID */
568   check_id_read_int (info, index, chapter_segment_uid,
569       sizeof (chapter_segment_uid), &value);
570
571   /* optional segment edition UID */
572   check_id_read_int (info, index, chapter_segment_edition_uid,
573       sizeof (chapter_segment_edition_uid), &value);
574
575   /* optional physical equiv */
576   check_id_read_int (info, index, chapter_physical_equiv,
577       sizeof (chapter_physical_equiv), &value);
578
579   /* optional chapter track */
580   if (check_id (info, index, chapter_track, sizeof (chapter_track), &len)) {
581     fail_unless (check_id_read_int (info, index, chapter_track_nb,
582             sizeof (chapter_track_nb), &value));
583   }
584
585   /* FIXME: there can be several chapter displays */
586   if (check_id (info, index, chapter_display, sizeof (chapter_display), &len)) {
587     /* chapter display */
588     fail_unless (check_id (info, index, chapter_string,
589             sizeof (chapter_string), &len));
590
591     tags = gst_toc_entry_get_tags (toc_entry);
592     if (gst_tag_list_get_tag_size (tags, GST_TAG_TITLE) > 0) {
593       gst_tag_list_get_string_index (tags, GST_TAG_TITLE, 0, &title);
594       fail_unless (memcmp (info->data + *index, title, strlen (title)) == 0);
595     }
596     *index += len;
597
598     fail_unless (check_id (info, index, chapter_language,
599             sizeof (chapter_language), &len));
600     /* TODO: define language - always "und" ATM */
601     *index += len;
602   }
603
604   /* TODO: add remaining fields (not used in current matroska-mux) */
605
606   g_snprintf (s_uid, sizeof (s_uid), "%" G_GINT64_FORMAT, uid);
607   internal_chapter = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, s_uid);
608   gst_toc_entry_append_sub_entry (internal_toc_entry, internal_chapter);
609
610   cur_sub_chap = gst_toc_entry_get_sub_entries (toc_entry);
611   while (cur_sub_chap != NULL && *index < last_offset) {
612     check_chapter (cur_sub_chap->data, internal_chapter, info,
613         index, last_offset);
614     cur_sub_chap = cur_sub_chap->next;
615   }
616
617   fail_unless (cur_sub_chap == NULL);
618 }
619
620 /* Check the reference toc against the muxed buffer */
621 static void
622 check_toc (GstToc * ref_toc, GstToc * internal_toc,
623     GstMapInfo * info, gsize * index)
624 {
625   guint64 len, value, uid;
626   gchar s_uid[32];
627   gint last_offset;
628   GList *cur_entry, *cur_chapter;
629   GstTocEntry *internal_edition;
630
631   guint8 edition_entry[] = { 0x45, 0xb9 };
632   guint8 edition_uid[] = { 0x45, 0xbc };
633   guint8 edition_flag_hidden[] = { 0x45, 0xbd };
634   guint8 edition_flag_default[] = { 0x45, 0xdb };
635   guint8 edition_flag_ordered[] = { 0x45, 0xdd };
636
637   /* edition entry */
638   fail_unless (check_id (info, index, edition_entry,
639           sizeof (edition_entry), &len));
640   last_offset = *index + (gint) len;
641
642   cur_entry = gst_toc_get_entries (ref_toc);
643   while (cur_entry != NULL && *index < last_offset) {
644     uid = 0;
645     check_id_read_int (info, index, edition_uid, sizeof (edition_uid), &uid);
646     g_snprintf (s_uid, sizeof (s_uid), "%" G_GINT64_FORMAT, uid);
647     internal_edition = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, s_uid);
648     gst_toc_append_entry (internal_toc, internal_edition);
649
650     fail_unless (check_id_read_int (info, index, edition_flag_hidden,
651             sizeof (edition_flag_hidden), &value));
652
653     fail_unless (check_id_read_int (info, index, edition_flag_default,
654             sizeof (edition_flag_default), &value));
655
656     /* optional */
657     check_id_read_int (info, index, edition_flag_ordered,
658         sizeof (edition_flag_ordered), &value);
659
660     cur_chapter = gst_toc_entry_get_sub_entries (cur_entry->data);
661     while (cur_chapter != NULL && *index < last_offset) {
662       check_chapter (cur_chapter->data, internal_edition, info,
663           index, last_offset);
664       cur_chapter = cur_chapter->next;
665     }
666     fail_unless (cur_chapter == NULL);
667
668     cur_entry = cur_entry->next;
669   }
670
671   fail_unless (cur_entry == NULL && *index == last_offset);
672 }
673
674 static GstTocEntry *
675 find_toc_entry (GstTocEntry * ref_toc_entry, GstTocEntry * internal_toc_entry,
676     guint64 uid)
677 {
678   GList *cur_ref_entry, *cur_internal_entry;
679   guint64 internal_uid;
680   GstTocEntry *result = NULL;
681
682   internal_uid = g_ascii_strtoull (gst_toc_entry_get_uid (internal_toc_entry),
683       NULL, 10);
684   if (uid == internal_uid) {
685     result = ref_toc_entry;
686   } else {
687     cur_ref_entry = gst_toc_entry_get_sub_entries (ref_toc_entry);
688     cur_internal_entry = gst_toc_entry_get_sub_entries (internal_toc_entry);
689     while (cur_ref_entry != NULL && cur_internal_entry != NULL) {
690       result = find_toc_entry (cur_ref_entry->data, cur_internal_entry->data,
691           uid);
692
693       if (result != NULL) {
694         break;
695       }
696
697       cur_ref_entry = cur_ref_entry->next;
698       cur_internal_entry = cur_internal_entry->next;
699     }
700   }
701
702   return result;
703 }
704
705 static void
706 find_and_check_tags (GstToc * ref_toc, GstToc * internal_toc, GstMapInfo * info,
707     guint64 uid, gchar * tag_name, gchar * tag_string)
708 {
709   GList *cur_ref_entry, *cur_internal_entry;
710   GstTocEntry *ref_toc_entry = NULL;
711   GstTagList *tags;
712   const gchar *tag_type;
713   gchar *cur_tag_string;
714
715   /* find the reference toc entry matching the UID */
716   cur_ref_entry = gst_toc_get_entries (ref_toc);
717   cur_internal_entry = gst_toc_get_entries (internal_toc);
718   while (cur_ref_entry != NULL && cur_internal_entry != NULL) {
719     ref_toc_entry = find_toc_entry (cur_ref_entry->data,
720         cur_internal_entry->data, uid);
721
722     if (ref_toc_entry != NULL) {
723       break;
724     }
725
726     cur_ref_entry = cur_ref_entry->next;
727     cur_internal_entry = cur_internal_entry->next;
728   }
729
730   fail_unless (ref_toc_entry != NULL);
731
732   if (g_strcmp0 (tag_name, "ARTIST") == 0) {
733     tag_type = GST_TAG_ARTIST;
734   } else if (g_strcmp0 (tag_name, "COMMENTS") == 0) {
735     tag_type = GST_TAG_COMMENT;
736   } else {
737     tag_type = NULL;
738   }
739
740   fail_unless (tag_type != NULL);
741
742   tags = gst_toc_entry_get_tags (ref_toc_entry);
743   fail_unless (gst_tag_list_get_tag_size (tags, tag_type) > 0);
744   gst_tag_list_get_string_index (tags, tag_type, 0, &cur_tag_string);
745   fail_unless (g_strcmp0 (cur_tag_string, tag_string) == 0);
746 }
747
748 static void
749 check_tags (GstToc * ref_toc, GstToc * internal_toc,
750     GstMapInfo * info, gsize * index)
751 {
752   gboolean found_tags = FALSE, must_check_tag = FALSE;
753   guint64 len, value, uid;
754   gsize last_offset, next_tag;
755   gchar *tag_name_str, *tag_string_str;
756   guint8 tags[] = { 0x12, 0x54, 0xc3, 0x67 };
757   guint8 tag[] = { 0x73, 0x73 };
758   guint8 tag_targets[] = { 0x63, 0xc0 };
759   guint8 tag_target_type_value[] = { 0x68, 0xca };
760   guint8 tag_target_type[] = { 0x63, 0xca };
761   guint8 tag_edition_uid[] = { 0x63, 0xc9 };
762   guint8 tag_chapter_uid[] = { 0x63, 0xc4 };
763   guint8 simple_tag[] = { 0x67, 0xc8 };
764   guint8 tag_name[] = { 0x45, 0xa3 };
765   guint8 tag_string[] = { 0x44, 0x87 };
766
767   if (info->size > *index + sizeof (tags)) {
768     for (; *index < info->size - sizeof (tags); ++(*index)) {
769       if (memcmp (info->data + *index, tags, sizeof (tags)) == 0) {
770         *index += sizeof (tags);
771
772         len = read_length (info, index);
773         last_offset = *index + len;
774
775         found_tags = TRUE;
776         break;
777       }
778     }
779   }
780
781   fail_unless (found_tags);
782
783   while (*index < last_offset) {
784     fail_unless (check_id (info, index, tag, sizeof (tag), &len));
785     next_tag = *index + len;
786
787     fail_unless (check_id (info, index, tag_targets,
788             sizeof (tag_targets), &len));
789
790     must_check_tag = FALSE;
791     check_id_read_int (info, index, tag_target_type_value,
792         sizeof (tag_target_type_value), &value);
793
794     if (check_id (info, index, tag_target_type, sizeof (tag_target_type), &len)) {
795       *index += len;
796     }
797
798     if (check_id_read_int (info, index, tag_chapter_uid,
799             sizeof (tag_chapter_uid), &uid)) {
800       must_check_tag = TRUE;
801     } else if (check_id_read_int (info, index, tag_edition_uid,
802             sizeof (tag_edition_uid), &uid)) {
803       must_check_tag = TRUE;
804     }
805
806     if (must_check_tag) {
807       fail_unless (check_id (info, index, simple_tag,
808               sizeof (simple_tag), &len));
809
810       fail_unless (check_id (info, index, tag_name, sizeof (tag_name), &len));
811       tag_name_str = g_strndup ((gchar *) info->data + *index, len);
812       *index += len;
813
814       fail_unless (check_id (info, index, tag_string, sizeof (tag_string),
815               &len));
816       tag_string_str = g_strndup ((gchar *) info->data + *index, len);
817       *index += len;
818
819       find_and_check_tags (ref_toc, internal_toc, info, uid,
820           tag_name_str, tag_string_str);
821
822       g_free (tag_name_str);
823       g_free (tag_string_str);
824     }
825
826     *index = next_tag;
827   }
828 }
829
830 static void
831 check_segment (GstToc * ref_toc, GstToc * internal_toc,
832     GstMapInfo * info, gsize * index)
833 {
834   guint8 matroska_segment[] = { 0x18, 0x53, 0x80, 0x67 };
835   guint8 matroska_seek_id_chapters[] = { 0x53, 0xab, 0x84,
836     0x10, 0x43, 0xA7, 0x70
837   };
838   guint8 matroska_seek_id_tags[] = { 0x53, 0xab, 0x84,
839     0x12, 0x54, 0xc3, 0x67
840   };
841   guint8 matroska_seek_pos[] = { 0x53, 0xac };
842   guint8 matroska_chapters[] = { 0x10, 0x43, 0xA7, 0x70 };
843
844   guint64 len, value, segment_offset, chapters_offset, tags_offset;
845   gboolean found_chapters_declaration = FALSE, found_tags_declaration = FALSE;
846
847   /* Segment */
848   fail_unless (info->size > sizeof (matroska_segment));
849   fail_unless (check_id (info, index, matroska_segment,
850           sizeof (matroska_segment), &len));
851
852   segment_offset = *index;
853
854   /* Search chapter declaration in seek head */
855   for (; *index < len - sizeof (matroska_seek_id_chapters); ++(*index)) {
856     if (memcmp (info->data + *index, matroska_seek_id_chapters,
857             sizeof (matroska_seek_id_chapters)) == 0) {
858       *index += sizeof (matroska_seek_id_chapters);
859
860       if (check_id_read_int (info, index, matroska_seek_pos,
861               sizeof (matroska_seek_pos), &value)) {
862         /* found chapter declaration */
863         found_chapters_declaration = TRUE;
864         chapters_offset = segment_offset + value;
865         break;
866       }
867     }
868   }
869
870   fail_unless (found_chapters_declaration);
871
872   *index = chapters_offset;
873   if (check_id (info, index, matroska_chapters,
874           sizeof (matroska_chapters), &len)) {
875     check_toc (ref_toc, internal_toc, info, index);
876   }
877
878   /* Search tags declaration in seek head */
879   for (*index = segment_offset; *index < len - sizeof (matroska_seek_id_tags);
880       ++(*index)) {
881     if (memcmp (info->data + *index, matroska_seek_id_tags,
882             sizeof (matroska_seek_id_tags)) == 0) {
883       *index += sizeof (matroska_seek_id_tags);
884
885       if (check_id_read_int (info, index, matroska_seek_pos,
886               sizeof (matroska_seek_pos), &value)) {
887         /* found tags declaration */
888         found_tags_declaration = TRUE;
889         tags_offset = segment_offset + value;
890         break;
891       }
892     }
893   }
894
895   fail_unless (found_tags_declaration);
896
897   *index = tags_offset;
898   check_tags (ref_toc, internal_toc, info, index);
899 }
900
901 static void
902 test_toc (gboolean with_edition)
903 {
904   GstHarness *h;
905   GstBuffer *inbuffer, *outbuffer, *merged_buffer;
906   GstMapInfo info;
907   guint64 len;
908   gsize index;
909   GstTocSetter *toc_setter;
910   GstToc *test_toc, *ref_toc, *internal_toc;
911
912   guint8 ebml_header[] = { 0x1a, 0x45, 0xdf, 0xa3 };
913
914   h = setup_matroskamux_harness (AC3_CAPS_STRING);
915
916   /* Make element seekable */
917   gst_pad_set_query_function (h->sinkpad, seekable_sinkpad_query);
918
919   toc_setter = GST_TOC_SETTER (h->element);
920   fail_unless (toc_setter != NULL);
921
922   if (with_edition) {
923     test_toc = new_reference_toc ();
924   } else {
925     test_toc = new_no_edition_toc ();
926   }
927   gst_toc_setter_set_toc (toc_setter, test_toc);
928   gst_toc_unref (test_toc);
929
930   inbuffer = gst_harness_create_buffer (h, 1);
931   gst_buffer_memset (inbuffer, 0, 0, 1);
932   GST_BUFFER_TIMESTAMP (inbuffer) = 0;
933   GST_BUFFER_DURATION (inbuffer) = 1 * GST_MSECOND;
934   fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h, inbuffer));
935
936   /* send eos to ensure everything is written */
937   fail_unless (gst_harness_push_event (h, gst_event_new_eos ()));
938
939   outbuffer = gst_harness_pull (h);
940   fail_unless (outbuffer != NULL);
941
942   /* Merge buffers */
943   merged_buffer = gst_buffer_new ();
944   while (outbuffer != NULL) {
945     if (outbuffer->offset == gst_buffer_get_size (merged_buffer)) {
946       gst_buffer_append_memory (merged_buffer,
947           gst_buffer_get_all_memory (outbuffer));
948     } else {
949       fail_unless (gst_buffer_map (outbuffer, &info, GST_MAP_READ));
950       gst_buffer_fill (merged_buffer, outbuffer->offset, info.data, info.size);
951       gst_buffer_unmap (outbuffer, &info);
952     }
953
954     gst_buffer_unref (outbuffer);
955     outbuffer = gst_harness_try_pull (h);
956   }
957
958   fail_unless (gst_buffer_map (merged_buffer, &info, GST_MAP_READ));
959   index = 0;
960
961   fail_unless (check_id (&info, &index, ebml_header,
962           sizeof (ebml_header), &len));
963   /* skip header */
964   index += len;
965
966   ref_toc = new_reference_toc ();
967   internal_toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL);
968   check_segment (ref_toc, internal_toc, &info, &index);
969   gst_toc_unref (internal_toc);
970   gst_toc_unref (ref_toc);
971
972   gst_buffer_unmap (merged_buffer, &info);
973   gst_harness_teardown (h);
974 }
975
976 GST_START_TEST (test_toc_with_edition)
977 {
978   test_toc (TRUE);
979 }
980
981 GST_END_TEST;
982
983 GST_START_TEST (test_toc_without_edition)
984 {
985   test_toc (FALSE);
986 }
987
988 GST_END_TEST;
989
990 static Suite *
991 matroskamux_suite (void)
992 {
993   Suite *s = suite_create ("matroskamux");
994   TCase *tc_chain = tcase_create ("general");
995
996   suite_add_tcase (s, tc_chain);
997   tcase_add_test (tc_chain, test_ebml_header_v1);
998   tcase_add_test (tc_chain, test_ebml_header_v2);
999   tcase_add_test (tc_chain, test_vorbis_header);
1000   tcase_add_test (tc_chain, test_block_group_v1);
1001   tcase_add_test (tc_chain, test_block_group_v2);
1002
1003   tcase_add_test (tc_chain, test_reset);
1004   tcase_add_test (tc_chain, test_link_webmmux_webm_sink);
1005   tcase_add_loop_test (tc_chain, test_timecodescale,
1006       0, G_N_ELEMENTS (timecodescales));
1007
1008   tcase_add_test (tc_chain, test_toc_with_edition);
1009   tcase_add_test (tc_chain, test_toc_without_edition);
1010   return s;
1011 }
1012
1013 GST_CHECK_MAIN (matroskamux);